Połącz dwie kolumny tekstu w ramce danych w pandas / python


485

Mam ramkę danych 20 x 4000 w Pythonie, używając pand. Dwie z tych kolumn są nazwane Yeari quarter. Chciałbym utworzyć zmienną o nazwie periodmake Year = 2000i quarter= q2into 2000q2.

Czy ktoś może w tym pomóc?

Odpowiedzi:


529

jeśli obie kolumny są łańcuchami, możesz je bezpośrednio łączyć:

df["period"] = df["Year"] + df["quarter"]

Jeśli jedna (lub obie) kolumny nie są napisane ciągiem, należy je najpierw przekonwertować,

df["period"] = df["Year"].astype(str) + df["quarter"]

Uważaj przy tym na Na!


Jeśli chcesz dołączyć wiele kolumn ciągów, możesz użyć agg:

df['period'] = df[['Year', 'quarter', ...]].agg('-'.join, axis=1)

Gdzie „-” jest separatorem.


13
Czy można dodać wiele kolumn razem bez wpisywania wszystkich kolumn? Powiedzmy add(dataframe.iloc[:, 0:10])na przykład?
Heisenberg

5
@Heisenberg Powinno to być możliwe dzięki wbudowanemu Pythonowi sum.
silvado

6
@silvado, czy możesz podać przykład dodawania wielu kolumn? Dziękuję
c1c1c1 25.10.16

6
Uważaj, musisz przede wszystkim zastosować map (str) do wszystkich kolumn, które nie są łańcuchami. jeśli ćwiartka to liczba, którą zrobiłbyś dataframe["period"] = dataframe["Year"].map(str) + dataframe["quarter"].map(str)map, to po prostu stosuje konwersję ciągów do wszystkich wpisów.
Ozgur Ozturk

13
To rozwiązanie może powodować problemy, jeśli masz wartości nan,

269
df = pd.DataFrame({'Year': ['2014', '2015'], 'quarter': ['q1', 'q2']})
df['period'] = df[['Year', 'quarter']].apply(lambda x: ''.join(x), axis=1)

Uzyskuje tę ramkę danych

   Year quarter  period
0  2014      q1  2014q1
1  2015      q2  2015q2

Ta metoda uogólnia na dowolną liczbę kolumn łańcuchowych, zastępując df[['Year', 'quarter']]dowolnym wycinkiem kolumny ramki danych, np df.iloc[:,0:2].apply(lambda x: ''.join(x), axis=1).

Możesz sprawdzić więcej informacji na temat metody Apply () tutaj


20
lambda x: ''.join(x)jest tylko ''.joinnie?
DSM,

6
@OzgurOzturk: ​​chodzi o to, że część lambda x: ''.join(x)konstrukcji lambda nic nie robi; to jak używanie lambda x: sum(x)zamiast po prostu sum.
DSM

4
Potwierdził ten sam wynik podczas używania ''.join, tj df['period'] = df[['Year', 'quarter']].apply(''.join, axis=1). :
Max Ghenis,

1
@Archie joinprzyjmuje tylko strwystąpienia iterowalne . Użyj a, mapaby przekonwertować je wszystkie, stra następnie użyj join.
John Strood

16
'-'. join (x.map (str))
Manjul

257

Małe zestawy danych (<150 rzędów)

[''.join(i) for i in zip(df["Year"].map(str),df["quarter"])]

lub nieco wolniej, ale bardziej kompaktowo:

df.Year.str.cat(df.quarter)

Większe zestawy danych (> 150 rzędów)

df['Year'].astype(str) + df['quarter']

AKTUALIZACJA: Wykres czasowy Pandas 0.23.4

wprowadź opis zdjęcia tutaj

Przetestujmy to na 200 000 wierszy DF:

In [250]: df
Out[250]:
   Year quarter
0  2014      q1
1  2015      q2

In [251]: df = pd.concat([df] * 10**5)

In [252]: df.shape
Out[252]: (200000, 2)

AKTUALIZACJA: nowe czasy przy użyciu Pandas 0.19.0

Czas bez optymalizacji CPU / GPU (posortowane od najszybszego do najwolniejszego):

In [107]: %timeit df['Year'].astype(str) + df['quarter']
10 loops, best of 3: 131 ms per loop

In [106]: %timeit df['Year'].map(str) + df['quarter']
10 loops, best of 3: 161 ms per loop

In [108]: %timeit df.Year.str.cat(df.quarter)
10 loops, best of 3: 189 ms per loop

In [109]: %timeit df.loc[:, ['Year','quarter']].astype(str).sum(axis=1)
1 loop, best of 3: 567 ms per loop

In [110]: %timeit df[['Year','quarter']].astype(str).sum(axis=1)
1 loop, best of 3: 584 ms per loop

In [111]: %timeit df[['Year','quarter']].apply(lambda x : '{}{}'.format(x[0],x[1]), axis=1)
1 loop, best of 3: 24.7 s per loop

Czas przy użyciu optymalizacji procesora / GPU:

In [113]: %timeit df['Year'].astype(str) + df['quarter']
10 loops, best of 3: 53.3 ms per loop

In [114]: %timeit df['Year'].map(str) + df['quarter']
10 loops, best of 3: 65.5 ms per loop

In [115]: %timeit df.Year.str.cat(df.quarter)
10 loops, best of 3: 79.9 ms per loop

In [116]: %timeit df.loc[:, ['Year','quarter']].astype(str).sum(axis=1)
1 loop, best of 3: 230 ms per loop

In [117]: %timeit df[['Year','quarter']].astype(str).sum(axis=1)
1 loop, best of 3: 230 ms per loop

In [118]: %timeit df[['Year','quarter']].apply(lambda x : '{}{}'.format(x[0],x[1]), axis=1)
1 loop, best of 3: 9.38 s per loop

Odpowiedz wkład od @ anton-vbr


Jaka różnica między 261 a 264 w twoim czasie?
Anton Protopopov

@AntonProtopopov najwyraźniej 100ms znikąd :)
Dennis Golomazov 10.10.16

@AntonProtopopov, myślę, że jest to mieszanka dwóch taktowań - jeden wykorzystał optymalizację procesora / karty graficznej, drugi nie. Zaktualizowałem swoją odpowiedź i umieściłem tam oba zestawy czasowe ...
MaxU 10.10.16

To użycie .sum () kończy się niepowodzeniem, jeśli wszystkie kolumny wyglądają tak, jakby mogły być liczbami całkowitymi (tzn. Są ciągowymi liczbami całkowitymi). Zamiast tego wygląda na to, że pandy konwertują je z powrotem na liczby przed zsumowaniem!
CPBL

@CPBL, wypróbuj to podejście:df.T.apply(lambda x: x.str.cat(sep=''))
MaxU

157

Sposób cat()na .strakcesor działa bardzo dobrze na to:

>>> import pandas as pd
>>> df = pd.DataFrame([["2014", "q1"], 
...                    ["2015", "q3"]],
...                   columns=('Year', 'Quarter'))
>>> print(df)
   Year Quarter
0  2014      q1
1  2015      q3
>>> df['Period'] = df.Year.str.cat(df.Quarter)
>>> print(df)
   Year Quarter  Period
0  2014      q1  2014q1
1  2015      q3  2015q3

cat() pozwala nawet dodać separator, więc na przykład załóżmy, że masz tylko liczby całkowite dla roku i okresu, możesz to zrobić:

>>> import pandas as pd
>>> df = pd.DataFrame([[2014, 1],
...                    [2015, 3]],
...                   columns=('Year', 'Quarter'))
>>> print(df)
   Year Quarter
0  2014       1
1  2015       3
>>> df['Period'] = df.Year.astype(str).str.cat(df.Quarter.astype(str), sep='q')
>>> print(df)
   Year Quarter  Period
0  2014       1  2014q1
1  2015       3  2015q3

Łączenie wielu kolumn to tylko kwestia przekazania listy serii lub ramki danych zawierającej wszystkie oprócz pierwszej kolumny jako parametr do str.cat()wywołania w pierwszej kolumnie (serii):

>>> df = pd.DataFrame(
...     [['USA', 'Nevada', 'Las Vegas'],
...      ['Brazil', 'Pernambuco', 'Recife']],
...     columns=['Country', 'State', 'City'],
... )
>>> df['AllTogether'] = df['Country'].str.cat(df[['State', 'City']], sep=' - ')
>>> print(df)
  Country       State       City                   AllTogether
0     USA      Nevada  Las Vegas      USA - Nevada - Las Vegas
1  Brazil  Pernambuco     Recife  Brazil - Pernambuco - Recife

Zauważ, że jeśli twoja ramka danych / serii pand ma wartości zerowe, musisz dołączyć parametr na_rep, aby zastąpić wartości NaN ciągiem, w przeciwnym razie połączona kolumna będzie domyślnie ustawiona na NaN.


12
Wydaje się to o wiele lepsze (być może także bardziej wydajne) niż lambdalub map; również czyta to najczystiej.
dwanderson

1
@ZakS, przekazując pozostałe kolumny jako ramkę danych zamiast serii jako pierwszy parametr do str.cat(). Zmienię odpowiedź
LeoRochael

Z jakiej wersji pand korzystasz? Dostaję ValueError: Czy chciałeś podać sepsłowo kluczowe? w pandach-0.23.4. Dzięki!
Qinqing Liu,

@QinqingLiu, przetestowałem je ponownie z pandas-0.23.4 i wydają się działać. Ten sepparametr jest konieczny tylko wtedy, gdy zamierzasz oddzielić części połączonego łańcucha. Jeśli pojawi się błąd, pokaż nam swój nieudany przykład.
LeoRochael,

31

Tym razem użycie funkcji lamba z string.format ().

import pandas as pd
df = pd.DataFrame({'Year': ['2014', '2015'], 'Quarter': ['q1', 'q2']})
print df
df['YearQuarter'] = df[['Year','Quarter']].apply(lambda x : '{}{}'.format(x[0],x[1]), axis=1)
print df

  Quarter  Year
0      q1  2014
1      q2  2015
  Quarter  Year YearQuarter
0      q1  2014      2014q1
1      q2  2015      2015q2

Pozwala to na pracę z nie-ciągami i formatowanie wartości w razie potrzeby.

import pandas as pd
df = pd.DataFrame({'Year': ['2014', '2015'], 'Quarter': [1, 2]})
print df.dtypes
print df

df['YearQuarter'] = df[['Year','Quarter']].apply(lambda x : '{}q{}'.format(x[0],x[1]), axis=1)
print df

Quarter     int64
Year       object
dtype: object
   Quarter  Year
0        1  2014
1        2  2015
   Quarter  Year YearQuarter
0        1  2014      2014q1
1        2  2015      2015q2

1
Znacznie szybciej: .apply (''. Join (x), axis = 1)
Ghanem

19

Prosta odpowiedź na twoje pytanie.

    year    quarter
0   2000    q1
1   2000    q2

> df['year_quarter'] = df['year'] + '' + df['quarter']

> print(df['year_quarter'])
  2000q1
  2000q2

3
zawiedzie, jeśli Yearnie jest łańcuchem
geher 10.09.19

4
użyciedf['Year'].astype(str) + '' + df['quarter'].astype(str)
Yedhrab

2
Jaki jest sens tego rozwiązania, skoro jest ono identyczne z najlepszą odpowiedzią?
AMC

14

Chociaż odpowiedź @silvado jest dobre, jeśli zmieni df.map(str)się df.astype(str)to będzie szybciej:

import pandas as pd
df = pd.DataFrame({'Year': ['2014', '2015'], 'quarter': ['q1', 'q2']})

In [131]: %timeit df["Year"].map(str)
10000 loops, best of 3: 132 us per loop

In [132]: %timeit df["Year"].astype(str)
10000 loops, best of 3: 82.2 us per loop

12

Załóżmy swoje dataframeIS dfz kolumnami Yeari Quarter.

import pandas as pd
df = pd.DataFrame({'Quarter':'q1 q2 q3 q4'.split(), 'Year':'2000'})

Załóżmy, że chcemy zobaczyć ramkę danych;

df
>>>  Quarter    Year
   0    q1      2000
   1    q2      2000
   2    q3      2000
   3    q4      2000

Na koniec połącz Yeari Quarternastępujące elementy.

df['Period'] = df['Year'] + ' ' + df['Quarter']

Teraz możesz print df zobaczyć wynikową ramkę danych.

df
>>>  Quarter    Year    Period
    0   q1      2000    2000 q1
    1   q2      2000    2000 q2
    2   q3      2000    2000 q3
    3   q4      2000    2000 q4

Jeśli nie chcesz odstępu między rokiem a kwartałem, po prostu usuń go, wykonując tę ​​czynność;

df['Period'] = df['Year'] + df['Quarter']

3
Określony jako ciągi znakówdf['Period'] = df['Year'].map(str) + df['Quarter'].map(str)
Stuber

Dostaję TypeError: Series cannot perform the operation +kiedy biegnę albo df2['filename'] = df2['job_number'] + '.' + df2['task_number']albo df2['filename'] = df2['job_number'].map(str) + '.' + df2['task_number'].map(str).
Karl Baker

Jednak df2['filename'] = df2['job_number'].astype(str) + '.' + df2['task_number'].astype(str)działało.
Karl Baker

@KarlBaker, myślę, że nie miałeś ciągów wejściowych. Ale cieszę się, że to rozgryzłeś. Jeśli spojrzysz na przykład, dataframektóry utworzyłem powyżej, zobaczysz, że wszystkie kolumny są strings.
Samuel Nde,

Jaki jest sens tego rozwiązania, skoro jest ono identyczne z najlepszą odpowiedzią?
AMC

10

Oto implementacja, którą uważam za bardzo wszechstronną:

In [1]: import pandas as pd 

In [2]: df = pd.DataFrame([[0, 'the', 'quick', 'brown'],
   ...:                    [1, 'fox', 'jumps', 'over'], 
   ...:                    [2, 'the', 'lazy', 'dog']],
   ...:                   columns=['c0', 'c1', 'c2', 'c3'])

In [3]: def str_join(df, sep, *cols):
   ...:     from functools import reduce
   ...:     return reduce(lambda x, y: x.astype(str).str.cat(y.astype(str), sep=sep), 
   ...:                   [df[col] for col in cols])
   ...: 

In [4]: df['cat'] = str_join(df, '-', 'c0', 'c1', 'c2', 'c3')

In [5]: df
Out[5]: 
   c0   c1     c2     c3                cat
0   0  the  quick  brown  0-the-quick-brown
1   1  fox  jumps   over   1-fox-jumps-over
2   2  the   lazy    dog     2-the-lazy-dog

FYI: Ta metoda działa świetnie w Pythonie 3, ale sprawia mi problemy w Pythonie 2.
Alex P. Miller

10

Ponieważ dane są wstawiane do ramki danych, to polecenie powinno rozwiązać problem:

df['period'] = df[['Year', 'quarter']].apply(lambda x: ' '.join(x.astype(str)), axis=1)

Ta odpowiedź jest identyczna ze starszą, bardziej popularną .
AMC

9

bardziej wydajny jest

def concat_df_str1(df):
    """ run time: 1.3416s """
    return pd.Series([''.join(row.astype(str)) for row in df.values], index=df.index)

a oto próba czasowa:

import numpy as np
import pandas as pd

from time import time


def concat_df_str1(df):
    """ run time: 1.3416s """
    return pd.Series([''.join(row.astype(str)) for row in df.values], index=df.index)


def concat_df_str2(df):
    """ run time: 5.2758s """
    return df.astype(str).sum(axis=1)


def concat_df_str3(df):
    """ run time: 5.0076s """
    df = df.astype(str)
    return df[0] + df[1] + df[2] + df[3] + df[4] + \
           df[5] + df[6] + df[7] + df[8] + df[9]


def concat_df_str4(df):
    """ run time: 7.8624s """
    return df.astype(str).apply(lambda x: ''.join(x), axis=1)


def main():
    df = pd.DataFrame(np.zeros(1000000).reshape(100000, 10))
    df = df.astype(int)

    time1 = time()
    df_en = concat_df_str4(df)
    print('run time: %.4fs' % (time() - time1))
    print(df_en.head(10))


if __name__ == '__main__':
    main()

końcowy, gdy sumużyje się (concat_df_str2), wynik nie jest po prostu concat, przejdzie do liczby całkowitej.


+1 Schludne rozwiązanie, pozwala nam również określić kolumny: np . df.values[:, 0:3]Lub df.values[:, [0,2]].
Trznadel śnieżny


6

Korzystanie zipmoże być jeszcze szybsze:

df["period"] = [''.join(i) for i in zip(df["Year"].map(str),df["quarter"])]

Wykres:

wprowadź opis zdjęcia tutaj

import pandas as pd
import numpy as np
import timeit
import matplotlib.pyplot as plt
from collections import defaultdict

df = pd.DataFrame({'Year': ['2014', '2015'], 'quarter': ['q1', 'q2']})

myfuncs = {
"df['Year'].astype(str) + df['quarter']":
    lambda: df['Year'].astype(str) + df['quarter'],
"df['Year'].map(str) + df['quarter']":
    lambda: df['Year'].map(str) + df['quarter'],
"df.Year.str.cat(df.quarter)":
    lambda: df.Year.str.cat(df.quarter),
"df.loc[:, ['Year','quarter']].astype(str).sum(axis=1)":
    lambda: df.loc[:, ['Year','quarter']].astype(str).sum(axis=1),
"df[['Year','quarter']].astype(str).sum(axis=1)":
    lambda: df[['Year','quarter']].astype(str).sum(axis=1),
    "df[['Year','quarter']].apply(lambda x : '{}{}'.format(x[0],x[1]), axis=1)":
    lambda: df[['Year','quarter']].apply(lambda x : '{}{}'.format(x[0],x[1]), axis=1),
    "[''.join(i) for i in zip(dataframe['Year'].map(str),dataframe['quarter'])]":
    lambda: [''.join(i) for i in zip(df["Year"].map(str),df["quarter"])]
}

d = defaultdict(dict)
step = 10
cont = True
while cont:
    lendf = len(df); print(lendf)
    for k,v in myfuncs.items():
        iters = 1
        t = 0
        while t < 0.2:
            ts = timeit.repeat(v, number=iters, repeat=3)
            t = min(ts)
            iters *= 10
        d[k][lendf] = t/iters
        if t > 2: cont = False
    df = pd.concat([df]*step)

pd.DataFrame(d).plot().legend(loc='upper center', bbox_to_anchor=(0.5, -0.15))
plt.yscale('log'); plt.xscale('log'); plt.ylabel('seconds'); plt.xlabel('df rows')
plt.show()

6

Najprostsze rozwiązanie:

Ogólne rozwiązanie

df['combined_col'] = df[['col1', 'col2']].astype(str).apply('-'.join, axis=1)

Pytanie specyficzne rozwiązanie

df['quarter_year'] = df[['quarter', 'year']].astype(str).apply(''.join, axis=1)

Określ preferowany separator w cudzysłowie przed .join


Czy to nie jest identyczne ze starszą, bardziej popularną odpowiedzią ?
AMC

5

W tym rozwiązaniu zastosowano etap pośredni kompresujący dwie kolumny DataFrame do pojedynczej kolumny zawierającej listę wartości. Działa to nie tylko dla ciągów, ale dla wszystkich rodzajów typów kolumn

import pandas as pd
df = pd.DataFrame({'Year': ['2014', '2015'], 'quarter': ['q1', 'q2']})
df['list']=df[['Year','quarter']].values.tolist()
df['period']=df['list'].apply(''.join)
print(df)

Wynik:

   Year quarter        list  period
0  2014      q1  [2014, q1]  2014q1
1  2015      q2  [2015, q2]  2015q2

wygląda na to, że inne typy nie będą działać. Mam TypeError: element sekwencji 1: oczekiwana instancja str, liczba zmiennoprzecinkowa znaleziona
Prometheus

najpierw zastosuj rzut do łańcucha. Operacja łączenia działa tylko na ciągi znaków
Markus Dutschke,

To rozwiązanie nie zadziała, aby połączyć dwie kolumny z innym typem, zobacz moją odpowiedź na poprawne rozwiązanie dla takiego przypadku.
Dobra wola

2

Jak wielu wspomniało wcześniej, należy przekonwertować każdą kolumnę na ciąg, a następnie użyć operatora plus, aby połączyć dwie kolumny ciągów. Możesz uzyskać dużą poprawę wydajności, używając NumPy.

%timeit df['Year'].values.astype(str) + df.quarter
71.1 ms ± 3.76 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%timeit df['Year'].astype(str) + df['quarter']
565 ms ± 22.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Chciałbym użyć wersji numpyified ale dostaję błąd: Wejście : df2['filename'] = df2['job_number'].values.astype(str) + '.' + df2['task_number'].values.astype(str)-> Output : TypeError: ufunc 'add' did not contain a loop with signature matching types dtype('<U21') dtype('<U21') dtype('<U21'). Zarówno numer_zadania, jak i numer_zadania są liczbami całkowitymi.
Karl Baker

To dlatego, że łączysz dwie tablice numpy. Działa, jeśli połączysz tablicę numpy z pandą Series. asdf['Year'].values.astype(str) + df.quarter
AbdulRehmanLiaqat

2

Myślę, że najlepszym sposobem na połączenie kolumn w pandy jest konwersja obu kolumn na liczby całkowite, a następnie na str.

df[['Year', 'quarter']] = df[['Year', 'quarter']].astype(int).astype(str)
df['Period']= df['Year'] + 'q' + df['quarter']

Konwertowanie obu kolumn na liczby całkowite Dlaczego najpierw przekonwertować na liczbę całkowitą ? Po usunięciu tej dziwności to rozwiązanie jest identyczne z obecną najlepszą odpowiedzią.
AMC

2

Oto moje podsumowanie powyższych rozwiązań, aby połączyć / połączyć dwie kolumny z wartościami int i str w nową kolumnę, używając separatora między wartościami kolumn. W tym celu działają trzy rozwiązania.

# be cautious about the separator, some symbols may cause "SyntaxError: EOL while scanning string literal".
# e.g. ";;" as separator would raise the SyntaxError

separator = "&&" 

# pd.Series.str.cat() method does not work to concatenate / combine two columns with int value and str value. This would raise "AttributeError: Can only use .cat accessor with a 'category' dtype"

df["period"] = df["Year"].map(str) + separator + df["quarter"]
df["period"] = df[['Year','quarter']].apply(lambda x : '{} && {}'.format(x[0],x[1]), axis=1)
df["period"] = df.apply(lambda x: f'{x["Year"]} && {x["quarter"]}', axis=1)

Dziękuję Ci! Twoje rozwiązanie z f-stringiem było właśnie tym, co miałem nadzieję znaleźć !!!
leerssej

1

Zastosowanie .combine_first.

df['Period'] = df['Year'].combine_first(df['Quarter'])

To nie jest poprawne. .combine_firstspowoduje 'Year'zapisanie wartości w 'Period', lub, jeśli jest Null, wartość z 'Quarter'. Nie połączy dwóch łańcuchów i nie zapisze ich w 'Period'.
Steve G.

To całkowicie błędne.
AMC

0
def madd(x):
    """Performs element-wise string concatenation with multiple input arrays.

    Args:
        x: iterable of np.array.

    Returns: np.array.
    """
    for i, arr in enumerate(x):
        if type(arr.item(0)) is not str:
            x[i] = x[i].astype(str)
    return reduce(np.core.defchararray.add, x)

Na przykład:

data = list(zip([2000]*4, ['q1', 'q2', 'q3', 'q4']))
df = pd.DataFrame(data=data, columns=['Year', 'quarter'])
df['period'] = madd([df[col].values for col in ['Year', 'quarter']])

df

    Year    quarter period
0   2000    q1  2000q1
1   2000    q2  2000q2
2   2000    q3  2000q3
3   2000    q4  2000q4

0

Można użyć metody przypisania DataFrame :

df= (pd.DataFrame({'Year': ['2014', '2015'], 'quarter': ['q1', 'q2']}).
  assign(period=lambda x: x.Year+x.quarter ))

-1
dataframe["period"] = dataframe["Year"].astype(str).add(dataframe["quarter"])

lub jeśli wartości są takie jak [2000] [4] i chcesz zrobić [2000q4]

dataframe["period"] = dataframe["Year"].astype(str).add('q').add(dataframe["quarter"]).astype(str)

podstawiając .astype(str)z .map(str)prac zbyt.


Jest to w zasadzie identyczna z najwyższą odpowiedzią.
AMC
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.