Combina due colonne di testo in dataframe in panda / python


488

Ho un dataframe 20 x 4000 in Python usando i panda. Due di queste colonne sono denominate Yeare quarter. Mi piacerebbe creare una variabile chiamata periodche fa Year = 2000e quarter= q2in 2000q2.

Qualcuno può aiutare con quello?

Risposte:


531

se entrambe le colonne sono stringhe, puoi concatenarle direttamente:

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

Se una (o entrambe) le colonne non sono tipizzate in stringa, dovresti prima convertirle (loro),

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

Fai attenzione alle NaN mentre fai questo!


Se è necessario unire più colonne di stringa, è possibile utilizzare agg:

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

Dove "-" è il separatore.


13
È possibile aggiungere più colonne insieme senza digitare tutte le colonne? Diciamo add(dataframe.iloc[:, 0:10])per esempio?
Heisenberg,

5
@Heisenberg Questo dovrebbe essere possibile con il Python incorporato sum.
silvado,

6
@silvado potresti fare un esempio per aggiungere più colonne? Grazie
c1c1c1,

6
Fai attenzione, devi prima applicare map (str) a tutte le colonne che non sono stringhe. se il quarto fosse un numero che faresti la dataframe["period"] = dataframe["Year"].map(str) + dataframe["quarter"].map(str)mappa sta semplicemente applicando la conversione di stringhe a tutte le voci.
Ozgur Ozturk,

13
Questa soluzione può creare problemi se hai valori di nan, attenzione

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

Produce questo frame di dati

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

Questo metodo si generalizza a un numero arbitrario di colonne stringa sostituendo df[['Year', 'quarter']]con qualsiasi sezione di colonna del tuo frame di dati, ad es df.iloc[:,0:2].apply(lambda x: ''.join(x), axis=1).

Puoi controllare maggiori informazioni sul metodo apply () qui


20
lambda x: ''.join(x)è solo ''.joinno?
DSM,

6
@OzgurOzturk: ​​il punto è che la parte lambda della lambda x: ''.join(x)costruzione non fa nulla; è come usare lambda x: sum(x)invece di solo sum.
DSM

4
Confermato stesso risultato quando si usa ''.join, cioè: df['period'] = df[['Year', 'quarter']].apply(''.join, axis=1).
Max Ghenis,

1
@Archie joinaccetta solo stristanze in iterabile. Utilizzare a mapper convertirli tutti in, strquindi utilizzare join.
John Strood,

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

257

Piccoli set di dati (<150rows)

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

o leggermente più lento ma più compatto:

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

Set di dati più grandi (> 150rows)

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

AGGIORNAMENTO: Grafico temporale Pandas 0.23.4

inserisci qui la descrizione dell'immagine

Proviamo su DF 200 righe:

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)

AGGIORNAMENTO: nuovi tempi usando Panda 0.19.0

Temporizzazione senza ottimizzazione CPU / GPU (ordinata dal più veloce al più lento):

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

Temporizzazione mediante ottimizzazione CPU / 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

Risposta di contributo di @ anton-vbr


Che differenza c'è tra 261 e 264 nel tuo tempismo?
Anton Protopopov,

@AntonProtopopov apparentemente 100ms dal nulla :)
Dennis Golomazov

@AntonProtopopov, suppongo sia una combinazione di due tempi: uno ha usato l'ottimizzazione CPU / GPU, un altro no. Ho aggiornato la mia risposta e ho inserito entrambi i set di temporizzazione ...
MaxU

Questo uso di .sum () fallisce Se tutte le colonne sembrano essere numeri interi (cioè sono forme stringa di numeri interi). Invece, sembra che i panda li convertano nuovamente in valori numerici prima di sommarli!
CPBL,

@CPBL, prova questo approccio:df.T.apply(lambda x: x.str.cat(sep=''))
MaxU

157

Il metodo cat()della .strfunzione di accesso funziona molto bene per questo:

>>> 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() ti consente anche di aggiungere un separatore, quindi, ad esempio, supponi di avere solo numeri interi per anno e periodo, puoi farlo:

>>> 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

Unire più colonne è solo una questione di passare un elenco di serie o un frame di dati contenente tutte tranne la prima colonna come parametro da str.cat()invocare sulla prima colonna (Serie):

>>> 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

Nota che se il tuo frame di dati panda / serie ha valori nulli, devi includere il parametro na_rep per sostituire i valori NaN con una stringa, altrimenti la colonna combinata verrà automaticamente impostata su NaN.


12
Questo sembra molto meglio (forse anche più efficiente) di lambdao map; inoltre legge solo in modo più pulito.
dwanderson,

1
@ZakS, passando le colonne rimanenti come frame di dati anziché una serie come primo parametro a str.cat().
Modificherò

Quale versione di Panda stai usando? Ottengo ValueError: intendevi fornire una sepparola chiave? in panda-0.23.4. Grazie!
Qinqing Liu,

@QinqingLiu, li ho ritestati con panda-0.23.4 e sembrano funzionare. Il sepparametro è necessario solo se si intende separare le parti della stringa concatenata. Se ricevi un errore, ti preghiamo di mostrarci il tuo esempio di errore.
LeoRochael,

31

L'uso di una funzione lamba questa volta con 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

Ciò consente di lavorare con non stringhe e riformattare i valori secondo necessità.

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
Molto più veloce: .apply (''. Join (x), axis = 1)
Ghanem

19

Risposta semplice per la tua domanda.

    year    quarter
0   2000    q1
1   2000    q2

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

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

3
fallirà se Yearnon è una stringa
geher

4
usodf['Year'].astype(str) + '' + df['quarter'].astype(str)
Yedhrab l'

2
Qual è esattamente il punto di questa soluzione, poiché è identica alla risposta migliore?
AMC

14

Anche se la risposta @silvado è buona se si df.map(str)passa ad df.astype(str)essa sarà più veloce:

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

Supponiamo che il tuo dataframesia dfcon colonne Yeare Quarter.

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

Supponiamo di voler vedere il frame di dati;

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

Infine, concatenare il Yeare il Quarterseguente.

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

Ora puoi print df vedere il frame di dati risultante.

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

Se non si desidera lo spazio tra l'anno e il trimestre, è sufficiente rimuoverlo facendo;

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

3
Specificato come stringhedf['Period'] = df['Year'].map(str) + df['Quarter'].map(str)
Stuber

Ricevo TypeError: Series cannot perform the operation +quando corro df2['filename'] = df2['job_number'] + '.' + df2['task_number']o df2['filename'] = df2['job_number'].map(str) + '.' + df2['task_number'].map(str).
Karl Baker,

Tuttavia, df2['filename'] = df2['job_number'].astype(str) + '.' + df2['task_number'].astype(str)ha funzionato.
Karl Baker,

@KarlBaker, penso che tu non abbia avuto stringhe nei tuoi input. Ma sono contento che tu l'abbia capito. Se guardi l'esempio dataframeche ho creato sopra, vedrai che tutte le colonne sono strings.
Samuel Nde

Qual è esattamente il punto di questa soluzione, poiché è identica alla risposta migliore?
AMC

10

Ecco un'implementazione che trovo molto versatile:

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: Questo metodo funziona alla grande con Python 3, ma mi dà problemi in Python 2.
Alex P. Miller

10

Man mano che i tuoi dati vengono inseriti in un dataframe, questo comando dovrebbe risolvere il tuo problema:

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

Questa risposta è identica a una più vecchia, più popolare .
AMC

9

è più efficiente

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

ed ecco un time test:

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()

final, quando sumviene utilizzato (concat_df_str2), il risultato non è semplicemente concat, ma passerà all'intero.


+1 soluzione chiara, questo ci permette anche di specificare le colonne: ad esempio df.values[:, 0:3]o df.values[:, [0,2]].
Zigolo delle nevi,

9

generalizzando su più colonne, perché no:

columns = ['whatever', 'columns', 'you', 'choose']
df['period'] = df[columns].astype(str).sum(axis=1)

Sembra bello ma cosa succede se voglio aggiungere un delimitatore tra le stringhe, come '-'?
Odisseo,

@Odisseo vedere questa risposta stackoverflow.com/questions/19377969/...
Geher

6

L'uso zippotrebbe essere ancora più veloce:

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

Grafico:

inserisci qui la descrizione dell'immagine

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

Soluzione più semplice:

Soluzione generica

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

Soluzione specifica per domande

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

Specificare il delimitatore preferito all'interno delle virgolette prima di .join


Non è identico a una risposta più vecchia e più popolare ?
AMC

5

Questa soluzione utilizza un passaggio intermedio comprimendo due colonne di DataFrame in una singola colonna contenente un elenco di valori. Questo funziona non solo per le stringhe ma per tutti i tipi di tipi di colonna

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)

Risultato:

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

sembra che altri tipi non funzionino. Ho ottenuto un TypeError: elemento sequenza 1: istanza str prevista, float trovato
Prometeo

applica prima un cast alla stringa. L'operazione di join funziona solo per le stringhe
Markus Dutschke, del

Questa soluzione non funzionerà per combinare due colonne con tipi diversi, vedere la mia risposta per la soluzione corretta per tale caso.
Buona volontà

2

Come molti hanno già detto in precedenza, è necessario convertire ogni colonna in stringa e quindi utilizzare l'operatore più per combinare due colonne di stringa. È possibile ottenere un notevole miglioramento delle prestazioni utilizzando 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)

Mi piacerebbe utilizzare la versione numpyified ma sto ottenendo un errore: Input : 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'). Sia job_number che task_number sono ints.
Karl Baker,

Questo perché stai combinando due array intorpiditi. Funziona se si combina un array intorpidito con la serie Panda. asdf['Year'].values.astype(str) + df.quarter
AbdulRehmanLiaqat,

2

Penso che il modo migliore per combinare le colonne in panda sia convertendo entrambe le colonne in numeri interi e poi in str.

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

convertire entrambe le colonne in numeri interi Perché prima convertirle in int? Una volta rimossa quella stranezza, questa soluzione è identica alla risposta principale corrente.
AMC

2

Ecco il mio riassunto delle soluzioni precedenti per concatenare / combinare due colonne con valore int e str in una nuova colonna, usando un separatore tra i valori delle colonne. Tre soluzioni funzionano a questo scopo.

# 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)

Grazie! La tua soluzione f-string era proprio quello che speravo di trovare !!!
leerssej

1

Usa .combine_first.

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

Questo non è corretto .combine_firstcomporterà la 'Year'memorizzazione del valore 'Period'o, se è Null, il valore da 'Quarter'. Non concatenerà le due stringhe e le memorizzerà 'Period'.
Steve G,

Questo è assolutamente sbagliato.
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)

Per esempio:

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

Si può utilizzare assegnazione metodo di 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"])

o se i valori sono come [2000] [4] e vogliono fare [2000q4]

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

sostituendo anche .astype(str)con le .map(str)opere.


Questo è essenzialmente identico alla risposta principale.
AMC
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.