Combinez deux colonnes de texte dans une trame de données en pandas / python

488

J'ai une trame de données 20 x 4000 en Python utilisant des pandas. Deux de ces colonnes sont nommées Yearet quarter. Je voudrais créer une variable appelée periodqui fait Year = 2000et quarter= q2en 2000q2.

Quelqu'un peut-il aider avec ça?

user2866103
la source

Réponses:

531

si les deux colonnes sont des chaînes, vous pouvez les concaténer directement:

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

Si l'une des colonnes (ou les deux) ne sont pas de type chaîne, vous devez d'abord les convertir,

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

Méfiez-vous des NaN lorsque vous faites cela!


Si vous devez joindre plusieurs colonnes de chaînes, vous pouvez utiliser agg:

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

Où "-" est le séparateur.

silvado
la source
13
Est-il possible d'ajouter plusieurs colonnes ensemble sans taper toutes les colonnes? Disons add(dataframe.iloc[:, 0:10])par exemple?
Heisenberg
5
@Heisenberg Cela devrait être possible avec le module intégré Python sum.
silvado
6
@silvado pourriez-vous faire un exemple pour ajouter plusieurs colonnes? Merci
c1c1c1
6
Attention, vous devez appliquer map (str) à toutes les colonnes qui ne sont pas des chaînes en premier lieu. si quart était un nombre que vous feriez, la dataframe["period"] = dataframe["Year"].map(str) + dataframe["quarter"].map(str)carte applique simplement la conversion de chaîne à toutes les entrées.
Ozgur Ozturk
13
Cette solution peut créer des problèmes si vous avez des valeurs nan, e attention
269
df = pd.DataFrame({'Year': ['2014', '2015'], 'quarter': ['q1', 'q2']})
df['period'] = df[['Year', 'quarter']].apply(lambda x: ''.join(x), axis=1)

Donne cette trame de données

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

Cette méthode se généralise à un nombre arbitraire de colonnes de chaîne en remplaçant df[['Year', 'quarter']]par n'importe quelle tranche de colonne de votre trame de données, par exempledf.iloc[:,0:2].apply(lambda x: ''.join(x), axis=1) .

Vous pouvez vérifier plus d'informations sur la méthode apply () ici

Russ
la source
20
lambda x: ''.join(x)c'est juste ''.join, non?
DSM
6
@OzgurOzturk: ​​le fait est que la partie lambda de la lambda x: ''.join(x)construction ne fait rien; c'est comme utiliser lambda x: sum(x)au lieu de juste sum.
DSM
4
Confirmé même résultat lors de l' utilisation ''.join, à savoir: df['period'] = df[['Year', 'quarter']].apply(''.join, axis=1).
Max Ghenis du
1
@Archie joinne prend que des strinstances dans un itérable. Utilisez un mappour les convertir tous str, puis utilisez join.
John Strood
16
'-'. join (x.map (str))
Manjul
257

Petits ensembles de données (<150 lignes)

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

ou légèrement plus lent mais plus compact:

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

Ensembles de données plus grands (> 150 lignes)

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

MISE À JOUR: Graphique de synchronisation Pandas 0.23.4

entrez la description de l'image ici

Testons-le sur 200K lignes 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)

MISE À JOUR: nouveaux timings utilisant Pandas 0.19.0

Timing sans optimisation CPU / GPU (trié du plus rapide au plus lent):

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

Timing utilisant l'optimisation 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

Répondre contribution par @ anton-vbr

MaxU
la source
Quelle différence entre 261 et 264 dans votre timing?
Anton Protopopov
@AntonProtopopov apparemment à 100 ms de nulle part :)
Dennis Golomazov
@AntonProtopopov, je suppose que c'est un mélange de deux synchronisations - une optimisation CPU / GPU utilisée, une autre non. J'ai mis à jour ma réponse et mis les deux ensembles de synchronisation là ...
MaxU
Cette utilisation de .sum () échoue Si toutes les colonnes semblent être des entiers (c'est-à-dire des chaînes de nombres entiers). Au lieu de cela, il semble que les pandas les reconvertissent en numérique avant de résumer!
CPBL
@CPBL, essayez cette approche:df.T.apply(lambda x: x.str.cat(sep=''))
MaxU
157

La méthode cat()de l' .straccesseur fonctionne très bien pour cela:

>>> 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() vous permet même d'ajouter un séparateur. Par exemple, supposons que vous ne disposiez que d'entiers pour l'année et la période, vous pouvez le faire:

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

Rejoindre plusieurs colonnes consiste simplement à passer soit une liste de séries, soit une trame de données contenant tout sauf la première colonne comme paramètre à str.cat()appeler sur la première colonne (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

Notez que si votre base de données / série pandas a des valeurs nulles, vous devez inclure le paramètre na_rep pour remplacer les valeurs NaN par une chaîne, sinon la colonne combinée sera par défaut NaN.

LeoRochael
la source
12
Cela semble bien meilleur (peut-être plus efficace aussi) que lambdaou map; en outre, il se lit très simplement.
dwanderson
1
@ZakS, en passant les colonnes restantes comme une trame de données au lieu d'une série comme premier paramètre str.cat(). Je vais modifier la réponse
LeoRochael
Quelle version de pandas utilisez-vous? J'obtiens ValueError: Vouliez-vous fournir un sepmot clé? chez les pandas-0,23.4. Merci!
Qinqing Liu
@QinqingLiu, je les ai retestés avec pandas-0.23.4 et ils semblent fonctionner. Le sepparamètre n'est nécessaire que si vous avez l'intention de séparer les parties de la chaîne concaténée. Si vous obtenez une erreur, veuillez nous montrer votre exemple d'échec.
LeoRochael
31

Utilisation d'une fonction lamba cette fois avec 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

Cela vous permet de travailler avec des non-chaînes et de reformater les valeurs selon vos besoins.

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
la source
1
Beaucoup plus rapide: .apply (''. Join (x), axis = 1)
Ghanem
19

Réponse simple à votre question.

    year    quarter
0   2000    q1
1   2000    q2

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

> print(df['year_quarter'])
  2000q1
  2000q2
Bandham Manikanta
la source
3
échouera si ce Yearn'est pas une chaîne
geher
4
utilisationdf['Year'].astype(str) + '' + df['quarter'].astype(str)
Yedhrab
2
Quel est exactement le point de cette solution, car elle est identique à la première réponse?
AMC
14

Bien que la réponse @silvado est bon si vous changez df.map(str)à df.astype(str)ce sera plus rapide:

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
la source
12

Supposons que votre dataframeest dfavec des colonnes Yearet Quarter.

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

Supposons que nous voulons voir la trame de données;

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

Enfin, concaténez le Yearet le Quartercomme suit.

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

Vous pouvez maintenant print df voir la trame de données résultante.

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 vous ne voulez pas l'espace entre l'année et le trimestre, supprimez-le simplement en faisant;

df['Period'] = df['Year'] + df['Quarter']
Samuel Nde
la source
3
Spécifié sous forme de chaînesdf['Period'] = df['Year'].map(str) + df['Quarter'].map(str)
Stuber
Je reçois TypeError: Series cannot perform the operation +quand je cours df2['filename'] = df2['job_number'] + '.' + df2['task_number']ou df2['filename'] = df2['job_number'].map(str) + '.' + df2['task_number'].map(str).
Karl Baker
Cependant, df2['filename'] = df2['job_number'].astype(str) + '.' + df2['task_number'].astype(str)a fonctionné.
Karl Baker
@KarlBaker, je pense que vous n'aviez pas de chaînes dans votre entrée. Mais je suis content que vous ayez compris cela. Si vous regardez l'exemple dataframeque j'ai créé ci-dessus, vous verrez que toutes les colonnes sont strings.
Samuel Nde
Quel est exactement le point de cette solution, car elle est identique à la première réponse?
AMC
10

Voici une implémentation que je trouve très polyvalente:

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
la source
FYI: Cette méthode fonctionne très bien avec Python 3, mais me pose des problèmes en Python 2.
Alex P. Miller
10

Lorsque vos données sont insérées dans une trame de données, cette commande devrait résoudre votre problème:

df['period'] = df[['Year', 'quarter']].apply(lambda x: ' '.join(x.astype(str)), axis=1)
VickyK
la source
Cette réponse est identique à une réponse plus ancienne et plus populaire .
AMC
9

plus efficace est

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

et voici un test de temps:

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, lorsque sum(concat_df_str2) est utilisé, le résultat n'est pas simplement concat, il sera trans en entier.

Colin Wang
la source
+1 Solution soignée, cela nous permet également de spécifier les colonnes: par exemple df.values[:, 0:3]ou df.values[:, [0,2]].
Bruant des neiges le
9

généraliser à plusieurs colonnes, pourquoi pas:

columns = ['whatever', 'columns', 'you', 'choose']
df['period'] = df[columns].astype(str).sum(axis=1)
geher
la source
Ça a l'air cool mais que faire si je veux ajouter un délimiteur entre les cordes, comme '-'?
Odisseo
@Odisseo voir cette réponse stackoverflow.com/questions/19377969/…
geher
6

L'utilisation zippourrait être encore plus rapide:

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

Graphique:

entrez la description de l'image ici

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
la source
6

Solution la plus simple:

Solution générique

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

Question solution spécifique

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

Spécifiez le délimiteur préféré à l'intérieur des guillemets avant .join

Gil Baggio
la source
N'est-ce pas identique à une réponse plus ancienne et plus populaire ?
AMC
5

Cette solution utilise une étape intermédiaire de compression de deux colonnes du DataFrame en une seule colonne contenant une liste des valeurs. Cela fonctionne non seulement pour les chaînes mais pour toutes sortes de types de colonnes

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)

Résultat:

   Year quarter        list  period
0  2014      q1  [2014, q1]  2014q1
1  2015      q2  [2015, q2]  2015q2
Markus Dutschke
la source
on dirait que les autres dtypes ne fonctionneront pas. J'ai un TypeError: élément de séquence 1: instance de str attendue, flotteur trouvé
Prometheus
appliquer d'abord un cast à la chaîne. L'opération de jointure ne fonctionne que pour les chaînes
Markus Dutschke
Cette solution ne fonctionnera pas pour combiner deux colonnes avec un type différent, voir ma réponse pour la solution correcte pour un tel cas.
Bonne volonté
2

Comme beaucoup l'ont mentionné précédemment, vous devez convertir chaque colonne en chaîne, puis utiliser l'opérateur plus pour combiner deux colonnes de chaîne. Vous pouvez obtenir une amélioration importante des performances en utilisant 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
la source
Je voudrais utiliser la version numpyified mais je reçois une erreur: Entrée : df2['filename'] = df2['job_number'].values.astype(str) + '.' + df2['task_number'].values.astype(str)-> Sortie : TypeError: ufunc 'add' did not contain a loop with signature matching types dtype('<U21') dtype('<U21') dtype('<U21'). Job_number et task_number sont tous deux des entiers.
Karl Baker
C'est parce que vous combinez deux tableaux numpy. Cela fonctionne si vous combinez un tableau numpy avec la série pandas. asdf['Year'].values.astype(str) + df.quarter
AbdulRehmanLiaqat
2

Je pense que la meilleure façon de combiner les colonnes dans pandas est de convertir les deux colonnes en entier puis en str.

df[['Year', 'quarter']] = df[['Year', 'quarter']].astype(int).astype(str)
df['Period']= df['Year'] + 'q' + df['quarter']
Loochie
la source
conversion des deux colonnes en entier Pourquoi convertir d'abord en entier ? Une fois que vous avez supprimé cette bizarrerie, cette solution est identique à la première réponse actuelle.
AMC
2

Voici mon résumé des solutions ci-dessus pour concaténer / combiner deux colonnes avec des valeurs int et str dans une nouvelle colonne, en utilisant un séparateur entre les valeurs des colonnes. Trois solutions fonctionnent à cet effet.

# 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)
Bonne volonté
la source
Je vous remercie! Votre solution de f-string était exactement ce que j'espérais trouver !!!
leerssej
1

Utilisez .combine_first.

df['Period'] = df['Year'].combine_first(df['Quarter'])
Abul
la source
Ce n'est pas correct. .combine_firstentraînera soit la valeur d' 'Year'être stockée dans 'Period', soit, si elle est Null, la valeur de 'Quarter'. Il ne concatène pas les deux chaînes et ne les stocke pas 'Period'.
Steve G
C'est carrément faux.
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)

Par exemple:

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
la source
0

On peut utiliser assign méthode de dataframe :

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

ou si les valeurs sont comme [2000] [4] et veulent faire [2000q4]

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

substituer .astype(str)avec des .map(str)œuvres aussi.

xgg
la source
Ceci est essentiellement identique à la première réponse.
AMC