Combina dos columnas de texto en el marco de datos en pandas / python


488

Tengo un marco de datos de 20 x 4000 en Python usando pandas. Dos de estas columnas se nombran Yeary quarter. Me gustaría crear una variable llamada periodque hagaYear = 2000 y quarter= q2en 2000q2.

¿Alguien puede ayudar con eso?

Respuestas:


531

Si ambas columnas son cadenas, puede concatenarlas directamente:

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

Si una (o ambas) columnas no están escritas en cadena, primero debe convertirlas (ellas),

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

¡Cuidado con NaNs al ​​hacer esto!


Si necesita unir varias columnas de cadena, puede usar agg:

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

Donde "-" es el separador.


13
¿Es posible agregar varias columnas juntas sin escribir todas las columnas? Digamos add(dataframe.iloc[:, 0:10])por ejemplo?
Heisenberg

55
@Heisenberg Eso debería ser posible con Python incorporado sum.
silvado

66
@silvado, ¿podría dar un ejemplo para agregar varias columnas? Gracias
c1c1c1

66
Tenga cuidado, debe aplicar map (str) a todas las columnas que no son cadenas en primer lugar. si el cuarto fuera un número que haría, el dataframe["period"] = dataframe["Year"].map(str) + dataframe["quarter"].map(str)mapa solo aplica la conversión de cadenas a todas las entradas.
Ozgur Ozturk

13
Esta solución puede crear problemas si tiene valores

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

Produce este marco de datos

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

Este método se generaliza a un número arbitrario de columnas de cadena al reemplazar df[['Year', 'quarter']]con cualquier segmento de columna de su marco de datos, por ejemplodf.iloc[:,0:2].apply(lambda x: ''.join(x), axis=1) .

Puede consultar más información sobre el método apply () aquí


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

66
@OzgurOzturk: ​​el punto es que la parte lambda de la lambda x: ''.join(x)construcción no hace nada; es como usar en lambda x: sum(x)lugar de solo sum.
DSM

44
Confirmado mismo resultado cuando se utiliza ''.join, es decir: df['period'] = df[['Year', 'quarter']].apply(''.join, axis=1).
Max Ghenis

1
@Archie joinsolo toma strinstancias en un iterable. Use a mappara convertirlos a todos stry luego use join.
John Strood

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

257

Pequeños conjuntos de datos (<150rows)

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

o un poco más lento pero más compacto:

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

Conjuntos de datos más grandes (> 150rows)

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

ACTUALIZACIÓN: Gráfico de tiempo Pandas 0.23.4

ingrese la descripción de la imagen aquí

Probémoslo en 200K filas 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)

ACTUALIZACIÓN: nuevos tiempos usando Pandas 0.19.0

Tiempo sin optimización de CPU / GPU (ordenado del más rápido al más 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

Tiempo utilizando la optimización de 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

Respuesta contribución de @ anton-vbr


¿Qué diferencia entre 261 y 264 en su tiempo?
Anton Protopopov

@AntonProtopopov aparentemente 100ms de la nada :)
Dennis Golomazov

@AntonProtopopov, supongo que es una mezcla de dos tiempos: uno utilizó la optimización de CPU / GPU, otro no. He actualizado mi respuesta y puse los dos conjuntos de tiempos allí ...
MaxU

Este uso de .sum () falla si todas las columnas parecen ser enteros (es decir, son formas de cadena de enteros). En cambio, ¡parece que los pandas los vuelven a convertir en numéricos antes de sumar!
CPBL

@CPBL, pruebe este enfoque:df.T.apply(lambda x: x.str.cat(sep=''))
MaxU

157

El método cat()del.str descriptor de acceso funciona muy bien para esto:

>>> 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() incluso le permite agregar un separador, por ejemplo, suponga que solo tiene números enteros por año y período, puede hacer esto:

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

Unirse a varias columnas es solo una cuestión de pasar una lista de series o un marco de datos que contiene todos menos la primera columna como parámetro a str.cat()invocar en la primera columna (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

Tenga en cuenta que si su marco / serie de datos de pandas tiene valores nulos, debe incluir el parámetro na_rep para reemplazar los valores de NaN con una cadena, de lo contrario, la columna combinada será NaN predeterminada.


12
Esto parece mucho mejor (quizás también más eficiente) que lambdao map; También se lee más limpiamente.
dwanderson

1
@ZakS, pasando las columnas restantes como un marco de datos en lugar de una serie como primer parámetro str.cat(). Enmendaré la respuesta
LeoRochael

¿Qué versión de pandas estás usando? Obtengo ValueError: ¿quiso decir una seppalabra clave? en pandas-0.23.4. ¡Gracias!
Qinqing Liu

@QinqingLiu, volví a probarlos con pandas-0.23.4 y parecen funcionar. El sepparámetro solo es necesario si tiene la intención de separar las partes de la cadena concatenada. Si recibe un error, muéstrenos su ejemplo de falla.
LeoRochael

31

Uso de una función lamba esta vez 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

Esto le permite trabajar con valores sin formato y sin cadenas según sea necesario.

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
Mucho más rápido: .apply (''. Join (x), axis = 1)
Ghanem

19

Respuesta simple para su pregunta.

    year    quarter
0   2000    q1
1   2000    q2

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

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

3
fallará si Yearno es una cadena
geher

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

2
¿Cuál es exactamente el punto de esta solución, ya que es idéntica a la respuesta principal?
AMC

14

Aunque la respuesta @silvado es bueno si se cambia df.map(str)a df.astype(str)que será más rápido:

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

Supongamos que su dataframees dfcon columnas Yeary Quarter.

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

Supongamos que queremos ver el marco de datos;

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

Finalmente, concatene el Yeary el de la Quartersiguiente manera.

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

Ahora puede print df ver el marco de datos resultante.

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

Si no desea el espacio entre el año y el trimestre, simplemente quítelo haciendo;

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

3
Especificado como cadenasdf['Period'] = df['Year'].map(str) + df['Quarter'].map(str)
Stuber

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

Sin embargo, df2['filename'] = df2['job_number'].astype(str) + '.' + df2['task_number'].astype(str)funcionó.
Karl Baker,

@KarlBaker, creo que no tenía cadenas en su entrada. Pero me alegro de que te hayas dado cuenta. Si miras el ejemplo dataframeque creé arriba, verás que todas las columnas son strings.
Samuel Nde

¿Cuál es exactamente el punto de esta solución, ya que es idéntica a la respuesta principal?
AMC

10

Aquí hay una implementación que me parece muy versátil:

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: Este método funciona muy bien con Python 3, pero me da problemas en Python 2.
Alex P. Miller

10

A medida que sus datos se insertan en un marco de datos, este comando debería resolver su problema:

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

Esta respuesta es idéntica a una más antigua y más popular .
AMC

9

más eficiente es

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

y aquí hay una prueba de tiempo:

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, cuando sumse usa (concat_df_str2), el resultado no es simplemente concat, sino que se transmite a entero.


+1 Solución ordenada, esto también nos permite especificar las columnas: por ejemplo, df.values[:, 0:3]o df.values[:, [0,2]].
Empavesado de nieve

9

generalizando a múltiples columnas, por qué no:

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

Parece genial, pero ¿qué pasa si quiero agregar un delimitador entre las cadenas, como '-'?
Odisseo

@Odisseo mira esta respuesta stackoverflow.com/questions/19377969/…
geher

6

Usar zippodría ser aún más rápido:

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

Grafico:

ingrese la descripción de la imagen aquí

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

La solución más simple:

Solución Genérica

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

Pregunta solución específica

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

Especifique el delimitador preferido dentro de las comillas antes de .join


¿No es esto idéntico a una respuesta más antigua y popular ?
AMC

5

Esta solución utiliza un paso intermedio que comprime dos columnas del DataFrame en una sola columna que contiene una lista de los valores. Esto funciona no solo para cadenas sino también para todo tipo de tipos de columnas

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)

Resultado:

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

Parece que otros tipos no funcionan. Obtuve un TypeError: elemento de secuencia 1: instancia str esperada, flotante encontrado
Prometheus

primero aplique un yeso a la cuerda. La operación de unión funciona solo para cadenas
Markus Dutschke

Esta solución no funcionará para combinar dos columnas con dtype diferente, vea mi respuesta para la solución correcta para tal caso.
Buena voluntad

2

Como muchos han mencionado anteriormente, debe convertir cada columna a cadena y luego usar el operador más para combinar dos columnas de cadena. Puede obtener una gran mejora en el rendimiento utilizando 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)

Me gustaría usar la versión numpyified pero estoy recibiendo un error: Entrada : df2['filename'] = df2['job_number'].values.astype(str) + '.' + df2['task_number'].values.astype(str)-> salida : TypeError: ufunc 'add' did not contain a loop with signature matching types dtype('<U21') dtype('<U21') dtype('<U21'). Tanto job_number como task_number son ints.
Karl Baker,

Eso es porque estás combinando dos matrices numpy. Funciona si combina una matriz numpy con la serie pandas. comodf['Year'].values.astype(str) + df.quarter
AbdulRehmanLiaqat

2

Creo que la mejor manera de combinar las columnas en pandas es convirtiendo ambas columnas en un entero y luego en str.

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

convertir ambas columnas a entero ¿Por qué convertir a int primero? Una vez que elimine esa rareza, esta solución es idéntica a la respuesta principal actual.
AMC

2

Aquí está mi resumen de las soluciones anteriores para concatenar / combinar dos columnas con valores int y str en una nueva columna, usando un separador entre los valores de las columnas. Tres soluciones funcionan para este propósito.

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

¡Gracias! ¡Su solución f-string era justo lo que esperaba encontrar!
Leerssej

1

Uso .combine_first.

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

Esto no es correcto. .combine_firstdará como resultado que el valor 'Year'se almacene en 'Period', o, si es Nulo, el valor de 'Quarter'. No concatenará las dos cadenas y las almacenará 'Period'.
Steve G

Esto es completamente incorrecto.
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)

Por ejemplo:

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

Uno puede utilizar Asignar método de la trama de datos :

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 si los valores son como [2000] [4] y desea hacer [2000q4]

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

sustituyendo .astype(str)con .map(str)obras también.


Esto es esencialmente idéntico a la respuesta principal.
AMC
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.