在熊猫数据框中合并两列文本

我在Python中使用熊猫有一个20 x 4000的数据框。其中两列名为Yearquarter。我想创建一个名为period的变量,使Year = 2000quarter= q2变成2000q2

有人能帮忙吗?

1605491 次浏览

如果两列都是字符串,你可以直接连接它们:

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

如果一个(或两个)列不是字符串类型的,您应该首先转换它(它们),

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

这样做时要小心NaN!


如果需要连接多个字符串列,可以使用agg

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

其中“-”是分隔符。

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

生成此数据帧

   Year quarter  period0  2014      q1  2014q11  2015      q2  2015q2

此方法通过将df[['Year', 'quarter']]替换为数据框的任何列切片(例如df.iloc[:,0:2].apply(lambda x: ''.join(x), axis=1))来泛化为任意数量的字符串列。

您可以查看有关应用()方法这里的更多信息

虽然@silvado的答案很好,如果你将df.map(str)更改为df.astype(str),它会更快:

import pandas as pddf = 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

方法#1访问器中的#0对此非常有效:

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

cat()甚至允许您添加分隔符,例如,假设您只有年和周期的整数,您可以这样做:

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

连接多个列只是将包含除第一列之外的所有列的序列列表或数据帧作为参数传递给在第一列(Series)上调用的str.cat()

>>> 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                   AllTogether0     USA      Nevada  Las Vegas      USA - Nevada - Las Vegas1  Brazil  Pernambuco     Recife  Brazil - Pernambuco - Recife

请注意,如果您的熊猫数据框/系列具有空值,则需要包含参数na_rep以将NaN值替换为字符串,否则组合列将默认为NaN。

这次使用lamba函数string.format()。

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

这允许您使用非字符串并根据需要重新格式化值。

import pandas as pddf = pd.DataFrame({'Year': ['2014', '2015'], 'Quarter': [1, 2]})print df.dtypesprint df
df['YearQuarter'] = df[['Year','Quarter']].apply(lambda x : '{}q{}'.format(x[0],x[1]), axis=1)print df
Quarter     int64Year       objectdtype: objectQuarter  Year0        1  20141        2  2015Quarter  Year YearQuarter0        1  2014      2014q11        2  2015      2015q2

小数据集(<150行)

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

或稍慢但更紧凑:

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

更大的数据集(>150行)

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

更新:定时图熊猫0.23.4

在此处输入图片描述

让我们在200K行DF上测试它:

In [250]: dfOut[250]:Year quarter0  2014      q11  2015      q2
In [251]: df = pd.concat([df] * 10**5)
In [252]: df.shapeOut[252]: (200000, 2)

更新:使用Pandas的新计时0.19.0

定时没有CPU/GPU优化(从最快到最慢排序):

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

定时使用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

答案贡献@安东-vbr

这是一个我发现非常通用的实现:

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]: dfOut[5]:c0   c1     c2     c3                cat0   0  the  quick  brown  0-the-quick-brown1   1  fox  jumps   over   1-fox-jumps-over2   2  the   lazy    dog     2-the-lazy-dog
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)

例如:

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 period0   2000    q1  2000q11   2000    q2  2000q22   2000    q3  2000q33   2000    q4  2000q4

正如前面提到的,您必须将每个列转换为字符串,然后使用加号运算符组合两个字符串列。使用NumPy可以大大提高性能。

%timeit df['Year'].values.astype(str) + df.quarter71.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)

更有效的是

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

下面是一个时间测试:

import numpy as npimport 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()

最后,当使用sum(concat_df_str2)时,结果不是简单的concat,它将转换为整数。

使用.combine_first

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

使用zip可以更快:

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

图表:

在此处输入图片描述

import pandas as pdimport numpy as npimport timeitimport matplotlib.pyplot as pltfrom 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 = 10cont = Truewhile cont:lendf = len(df); print(lendf)for k,v in myfuncs.items():iters = 1t = 0while t < 0.2:ts = timeit.repeat(v, number=iters, repeat=3)t = min(ts)iters *= 10d[k][lendf] = t/itersif t > 2: cont = Falsedf = 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()

让我们假设你的dataframedf,列YearQuarter

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

假设我们想看到数据框;

df>>>  Quarter    Year0    q1      20001    q2      20002    q3      20003    q4      2000

最后,将YearQuarter连接如下。

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

您现在可以printdf查看生成的数据框。

df>>>  Quarter    Year    Period0   q1      2000    2000 q11   q2      2000    2000 q22   q3      2000    2000 q33   q4      2000    2000 q4

如果你不想要年和季度之间的空格,只需通过做来删除它;

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

可以使用数据帧分配方法:

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

此解决方案使用值的中间步骤将DataFrame的两列压缩为包含列表的一列。这不仅适用于字符串,也适用于所有类型的列类型

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

结果:

   Year quarter        list  period0  2014      q1  [2014, q1]  2014q11  2015      q2  [2015, q2]  2015q2

这是我对上述解决方案的总结,这些解决方案使用列值之间的分隔符将具有int和str值的两列连接/组合成一个新列。三个解决方案可用于此目的。

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

推广到多个列,为什么不:

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

我拿…

listofcols = ['col1','col2','col3']df['combined_cols'] = ''
for column in listofcols:df['combined_cols'] = df['combined_cols'] + ' ' + df[column]'''

您可以使用lambda:

combine_lambda = lambda x: '{}{}'.format(x.Year, x.quarter)

然后在创建新列时使用它:

df['period'] = df.apply(combine_lambda, axis = 1)

类似于@geher答案,但可以使用您喜欢的任何分隔符:

SEP = " "INPUT_COLUMNS_WITH_SEP = ",sep,".join(INPUT_COLUMNS).split(",")
df.assign(sep=SEP)[INPUT_COLUMNS_WITH_SEP].sum(axis=1)