Combine duas colunas de texto no dataframe em pandas / python

487

Eu tenho um dataframe 20 x 4000 em Python usando pandas. Duas dessas colunas são nomeadas Yeare quarter. Eu gostaria de criar uma variável chamada periodthat makes Year = 2000and quarter= q2into 2000q2.

Alguém pode ajudar com isso?

user2866103
fonte

Respostas:

530

se as duas colunas forem cadeias, concatená-las diretamente:

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

Se uma (ou ambas) das colunas não tiverem um tipo de string, você deve convertê-las (elas) primeiro,

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

Cuidado com os NaNs ao fazer isso!


Se você precisar ingressar em várias colunas de sequência, poderá usar agg:

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

Onde "-" é o separador.

silvado
fonte
13
É possível adicionar várias colunas sem digitar todas as colunas? Digamos, add(dataframe.iloc[:, 0:10])por exemplo?
Heisenberg
5
@Heisenberg Isso deve ser possível com o Python embutido sum.
Silvado
6
@silvado, você poderia fazer um exemplo para adicionar várias colunas? Obrigado
c1c1c1 25/10
6
Cuidado, você precisa aplicar o mapa (str) a todas as colunas que não são string em primeiro lugar. se trimestre fosse um número, você faria o dataframe["period"] = dataframe["Year"].map(str) + dataframe["quarter"].map(str)mapeamento apenas aplicando a conversão de string a todas as entradas.
Ozgur Ozturk
13
Esta solução pode criar problemas, se você tiver valores nan, e tenha cuidado
269
df = pd.DataFrame({'Year': ['2014', '2015'], 'quarter': ['q1', 'q2']})
df['period'] = df[['Year', 'quarter']].apply(lambda x: ''.join(x), axis=1)

Rende esse quadro de dados

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

Este método generaliza para um número arbitrário de colunas de sequência, substituindo df[['Year', 'quarter']]por qualquer fatia da coluna do seu quadro de dados, por exemplo df.iloc[:,0:2].apply(lambda x: ''.join(x), axis=1).

Você pode verificar mais informações sobre o método apply () aqui

Russ
fonte
20
lambda x: ''.join(x)é só ''.join, não?
DSM
6
@OzgurOzturk: ​​o ponto é que a parte lambda da lambda x: ''.join(x)construção não faz nada; é como usar em lambda x: sum(x)vez de apenas sum.
DSM
4
Confirmado mesmo resultado quando se usa ''.join, isto é,: df['period'] = df[['Year', 'quarter']].apply(''.join, axis=1).
Max Ghenis 10/10
1
@Archie joinleva apenas strinstâncias em um iterável. Use a mappara convertê-los todos em stre depois use join.
precisa
16
'-'. join (x.map (str))
Manjul
257

Conjuntos de dados pequenos (<150 linhas)

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

ou um pouco mais lento, mas mais compacto:

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

Conjuntos de dados maiores (> 150 linhas)

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

ATUALIZAÇÃO: Gráfico de tempo Pandas 0.23.4

insira a descrição da imagem aqui

Vamos testá-lo em 200 mil linhas 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)

UPDATE: novos horários usando o Pandas 0.19.0

Tempo sem otimização da CPU / GPU (classificado do mais rápido para o mais 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

Tempo usando otimização 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

Responder contribuição de @ anton-vbr

MaxU
fonte
Que diferença entre 261 e 264 no seu tempo?
Anton Protopopov
@AntonProtopopov aparentemente 100ms do nada :)
Dennis Golomazov
@AntonProtopopov, eu acho que é uma mistura de dois tempos - um usado otimização de CPU / GPU, outro não. Atualizei minha resposta e coloquei os dois intervalos de tempo lá ...
MaxU
Este uso de .sum () falha Se todas as colunas parecerem que poderiam ser números inteiros (ou seja, são formas de números inteiros em sequência). Em vez disso, parece que os pandas os convertem de volta para numéricos antes de somar!
CPBL
@CPBL, tente esta abordagem: df.T.apply(lambda x: x.str.cat(sep=''))
MaxU 26/17/17
157

O método cat()do .stracessador funciona muito bem para isso:

>>> 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() ainda permite adicionar um separador, por exemplo, suponha que você só tenha números inteiros por ano e período, faça o seguinte:

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

A união de várias colunas é apenas uma questão de passar uma lista de séries ou um quadro de dados contendo todos, exceto a primeira coluna, como um parâmetro a ser str.cat()invocado na primeira coluna (Série):

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

Observe que, se o dataframe / series do pandas tiver valores nulos, você precisará incluir o parâmetro na_rep para substituir os valores de NaN por uma sequência, caso contrário, a coluna combinada será padronizada como NaN.

LeoRochael
fonte
12
Isso parece muito melhor (talvez mais eficiente também) que lambdaou map; também apenas lê de maneira mais limpa.
Dwanderson 22/05
1
@ZakS, passando as colunas restantes como um quadro de dados em vez de uma série como o primeiro parâmetro para str.cat(). Vou alterar a resposta
LeoRochael
Qual versão do pandas você está usando? Recebo ValueError: você quis fornecer uma seppalavra - chave? nos pandas-0.23.4. Obrigado!
Qinqing Liu
@QinqingLiu, eu os testei novamente com pandas-0.23.4 e eles parecem funcionar. O sepparâmetro é necessário apenas se você pretende separar as partes da sequência concatenada. Se você receber um erro, mostre-nos o seu exemplo com falha.
LeoRochael
31

Agora, use uma função lamba com 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

Isso permite que você trabalhe com não-seqüências de caracteres e reformate valores, conforme necessário.

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
Bill Gale
fonte
1
Muito mais rápido: .apply (''. Join (x), axis = 1)
Ghanem
19

Resposta simples para sua pergunta.

    year    quarter
0   2000    q1
1   2000    q2

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

> print(df['year_quarter'])
  2000q1
  2000q2
Bandham Manikanta
fonte
3
falhará se Yearnão for uma string
geher 10/09/19
4
usedf['Year'].astype(str) + '' + df['quarter'].astype(str)
Yedhrab 11/09/19
2
Qual é exatamente o objetivo dessa solução, pois é idêntica à resposta principal?
AMC
14

Embora a resposta @silvado é bom se você mudar df.map(str)para df.astype(str)ele vai ser mais 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
Anton Protopopov
fonte
12

Suponhamos que você dataframeesteja dfcom colunas Yeare Quarter.

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

Suponha que queremos ver o quadro de dados;

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

Finalmente, concatenar o Yeareo Quarterda seguinte forma.

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

Agora você pode print df ver o quadro de dados 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

Se você não deseja o espaço entre o ano e o trimestre, basta removê-lo fazendo;

df['Period'] = df['Year'] + df['Quarter']
Samuel Nde
fonte
3
Especificado como seqüências de caracteresdf['Period'] = df['Year'].map(str) + df['Quarter'].map(str)
Stuber
Estou conseguindo TypeError: Series cannot perform the operation +quando corro um df2['filename'] = df2['job_number'] + '.' + df2['task_number']ou outro df2['filename'] = df2['job_number'].map(str) + '.' + df2['task_number'].map(str).
Karl Baker
No entanto, df2['filename'] = df2['job_number'].astype(str) + '.' + df2['task_number'].astype(str)funcionou.
Karl Baker
@KarlBaker, acho que você não teve seqüências de caracteres em sua entrada. Mas estou feliz que você tenha entendido isso. Se você observar o exemplo dataframeque criei acima, verá que todas as colunas são strings.
Samuel Nde
Qual é exatamente o objetivo dessa solução, pois é idêntica à resposta principal?
AMC
10

Aqui está uma implementação que eu acho muito 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
Pedro M Duarte
fonte
FYI: Este método funciona muito bem com Python 3, mas me dá problemas em Python 2.
Alex P. Miller
10

Como seus dados são inseridos em um quadro de dados, este comando deve resolver seu problema:

df['period'] = df[['Year', 'quarter']].apply(lambda x: ' '.join(x.astype(str)), axis=1)
VickyK
fonte
Essa resposta é idêntica a uma mais antiga e popular .
AMC
9

mais eficiente é

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

e aqui está um teste do tempo:

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 sum(concat_df_str2) é usado, o resultado não é simplesmente concat, ele será transferido para inteiro.

Colin Wang
fonte
Solução simples +1, isso também nos permite especificar as colunas: por exemplo, df.values[:, 0:3]ou df.values[:, [0,2]].
Snow bunting
9

generalizando para várias colunas, por que não:

columns = ['whatever', 'columns', 'you', 'choose']
df['period'] = df[columns].astype(str).sum(axis=1)
geher
fonte
Parece legal, mas e se eu quiser adicionar um delimitador entre as strings, como '-'?
Odisseo 2/10/19
@Odisseo veja esta resposta stackoverflow.com/questions/19377969/…
geher
6

O uso zippode ser ainda mais rápido:

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

Gráfico:

insira a descrição da imagem aqui

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()
Anton vBR
fonte
6

Solução mais simples:

Solução genérica

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

Pergunta specific solution

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

Especifique o delimitador preferido dentro das aspas antes de .join

Gil Baggio
fonte
Isso não é idêntico a uma resposta mais antiga e popular ?
AMC
5

Esta solução usa uma etapa intermediária compactando duas colunas do DataFrame em uma única coluna contendo uma lista dos valores. Isso funciona não apenas para strings, mas para todos os tipos de tipos de coluna

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
Markus Dutschke
fonte
parece que outros tipos não funcionam. Eu tenho um TypeError: o item 1 seqüência: Espera exemplo str, flutuador encontrou
Prometheus
aplique primeiro um elenco à sequência. A juntar-se obras de operação apenas para cordas
Markus Dutschke
Esta solução não funcionará para combinar duas colunas com tipos diferentes. Consulte minha resposta para a solução correta para esse caso.
Boa vontade
2

Como muitos mencionaram anteriormente, você deve converter cada coluna em sequência e, em seguida, usar o operador mais para combinar duas colunas de sequência. Você pode obter uma grande melhoria de desempenho usando o 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)
Ted Petrou
fonte
Eu gostaria de usar a versão numpyified mas eu estou recebendo um erro: Entrada : df2['filename'] = df2['job_number'].values.astype(str) + '.' + df2['task_number'].values.astype(str)-> Saída : TypeError: ufunc 'add' did not contain a loop with signature matching types dtype('<U21') dtype('<U21') dtype('<U21'). Job_number e task_number são ints.
Karl Baker
Isso porque você está combinando duas matrizes numpy. Funciona se você combinar uma matriz numpy com a série pandas. asdf['Year'].values.astype(str) + df.quarter
AbdulRehmanLiaqat
2

Eu acho que a melhor maneira de combinar as colunas nos pandas é convertendo ambas as colunas em número inteiro e depois em str.

df[['Year', 'quarter']] = df[['Year', 'quarter']].astype(int).astype(str)
df['Period']= df['Year'] + 'q' + df['quarter']
Loochie
fonte
convertendo as duas colunas em número inteiro Por que converter primeiro em int? Depois de remover essa singularidade, esta solução é idêntica à principal resposta atual.
AMC
2

Aqui está o meu resumo das soluções acima para concatenar / combinar duas colunas com o valor int e str em uma nova coluna, usando um separador entre os valores das colunas. Três soluções funcionam para esse fim.

# 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)
Boa vontade
fonte
Obrigado! Sua solução f-string era exatamente o que eu esperava encontrar !!!
leerssej
1

Use .combine_first.

df['Period'] = df['Year'].combine_first(df['Quarter'])
Abul
fonte
Isso não está correto. .combine_firstresultará no 'Year'armazenamento do valor 'Period'ou, se for nulo, no valor de 'Quarter'. Não concatenará as duas seqüências e as armazenará 'Period'.
Steve G
Isso é totalmente errado.
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 exemplo:

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
BMW
fonte
0

Pode-se usar atribuir método da trama de dados :

df= (pd.DataFrame({'Year': ['2014', '2015'], 'quarter': ['q1', 'q2']}).
  assign(period=lambda x: x.Year+x.quarter ))
Sergey
fonte
-1
dataframe["period"] = dataframe["Year"].astype(str).add(dataframe["quarter"])

ou se os valores são como [2000] [4] e desejam criar [2000q4]

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

substituindo .astype(str)por .map(str)obras também.

xgg
fonte
Isso é essencialmente idêntico à resposta principal.
AMC