将pandas数据框中的所有列连接起来

26

我有多个pandas数据帧,它们可能具有不同数量的列,这些列的数量通常在50到100之间变化。我需要创建一个最终列,该列只是将所有列连接起来的结果。基本上,该列的第一行中的字符串应该是所有列第一行字符串的总和(连接)。我编写了下面的循环,但我感觉可能有更好、更有效的方法来完成这个任务。您有任何关于如何做到这一点的想法吗?

num_columns = df.columns.shape[0]
col_names = df.columns.values.tolist()
df.loc[:, 'merged'] = ""
for each_col_ind in range(num_columns):
    print('Concatenating', col_names[each_col_ind])
    df.loc[:, 'merged'] = df.loc[:, 'merged'] + df[col_names[each_col_ind]]
6个回答

43

使用sum解决方案,但输出结果是float,因此需要进行intstr转换:

df['new'] = df.sum(axis=1).astype(int).astype(str)

使用apply函数和join的另一种解决方案,但是它是最慢的:

df['new'] = df.apply(''.join, axis=1)

最后一个非常快的 numpy解决方案 - 转换为numpy数组,然后使用'sum'函数:

df['new'] = df.values.sum(axis=1)

时间

df = pd.DataFrame({'A': ['1', '2', '3'], 'B': ['4', '5', '6'], 'C': ['7', '8', '9']})
#[30000 rows x 3 columns]
df = pd.concat([df]*10000).reset_index(drop=True)
#print (df)

cols = list('ABC')

#not_a_robot solution
In [259]: %timeit df['concat'] = pd.Series(df[cols].fillna('').values.tolist()).str.join('')
100 loops, best of 3: 17.4 ms per loop

In [260]: %timeit df['new'] = df[cols].astype(str).apply(''.join, axis=1)
1 loop, best of 3: 386 ms per loop

In [261]: %timeit df['new1'] = df[cols].values.sum(axis=1)
100 loops, best of 3: 6.5 ms per loop

In [262]: %timeit df['new2'] = df[cols].astype(str).sum(axis=1).astype(int).astype(str)
10 loops, best of 3: 68.6 ms per loop

如果某些列的数据类型不是object(显然是string),请使用DataFrame.astype进行强制类型转换:

df['new'] = df.astype(str).values.sum(axis=1)

有没有办法仅连接最后20列,但仅在存在数据的情况下连接特定列?此外,我希望设置分隔符,这样当您查看整个列时,可以看到它是如何分解的。 - Gary Dorman

24
df = pd.DataFrame({'A': ['1', '2', '3'], 'B': ['4', '5', '6'], 'C': ['7', '8', '9']})

df['concat'] = pd.Series(df.fillna('').values.tolist()).str.join('')

给我们:

df
Out[6]: 
   A  B  C concat
0  1  4  7    147
1  2  5  8    258
2  3  6  9    369
为了选择特定的列:

To select a given set of columns:

df['concat'] = pd.Series(df[['A', 'B']].fillna('').values.tolist()).str.join('')

df
Out[8]: 
   A  B  C concat
0  1  4  7     14
1  2  5  8     25
2  3  6  9     36

然而,我注意到这种方法有时会导致 NaN 出现在不应该出现的位置,所以这里提供另一种方式:

>>> from functools import reduce
>>> df['concat'] = df[cols].apply(lambda x: reduce(lambda a, b: a + b, x), axis=1)
>>> df
   A  B  C concat
0  1  4  7    147
1  2  5  8    258
2  3  6  9    369

尽管需要注意这种方法要慢得多:

$ python3 -m timeit 'import pandas as pd;from functools import reduce; df=pd.DataFrame({"a": ["this", "is", "a", "string"] * 5000, "b": ["this", "is", "a", "string"] * 5000});[df[["a", "b"]].apply(lambda x: reduce(lambda a, b: a + b, x)) for _ in range(10)]'
10 loops, best of 3: 451 msec per loop

对抗

$ python3 -m timeit 'import pandas as pd;from functools import reduce; df=pd.DataFrame({"a": ["this", "is", "a", "string"] * 5000, "b": ["this", "is", "a", "string"] * 5000});[pd.Series(df[["a", "b"]].fillna("").values.tolist()).str.join(" ") for _ in range(10)]'
10 loops, best of 3: 98.5 msec per loop

2
可能是因为我使用的是更新版本的Python,但我完全复制了你的代码,包括Dataframe,但它并没有起作用。我正在使用3.7.0版本。 - Chicken Sandwich No Pickles

15

由于我声望不够,无法进行评论,因此我会根据blacksite所回答的内容来撰写我的答案。

为了更清晰明了,LunchBox曾评论称此方法在Python 3.7.0上失败。对我而言,在Python 3.6.3上也失败了。以下是blacksite原始回答:

df['concat'] = pd.Series(df.fillna('').values.tolist()).str.join('')

这是我对Python 3.6.3的修改:

df['concat'] = pd.Series(df.fillna('').values.tolist()).map(lambda x: ''.join(map(str,x)))

1
谢谢, @bodily。我最终遇到了相同的情况,你的答案帮助了我。 - Gopinath S

3

对于我来说,使用numpy数组的解决方案非常好。

然而,需要注意的一件事情是,在从df.values获取numpy.ndarray时进行索引时,由于轴标签已从df.values中删除,因此需要小心。

因此,以上面提供的解决方案之一(我最常用的解决方案)为例:

df['concat'] = pd.Series(df.fillna('').values.tolist()).str.join('')

这一部分:

df.fillna('').values

不保留原始DataFrame的索引。 当DataFrame具有常见的0, 1, 2, ...行索引方案时,这不是问题,但是当DataFrame以其他方式进行索引时,此解决方案将无法工作。 您可以通过向pd.Series()添加index=参数来解决此问题:

df['concat'] = pd.Series(df.fillna('').values.tolist(), 
                         index=df.index).str.join('')

我总是添加index=参数,以确保安全,即使我确定DataFrame已经按行索引为0, 1, 2, ...


如何避免在此处出现Na值 - Niks

1

这种lambda方法在选择列和分隔符类型方面提供了一些灵活性

设置:

df = pd.DataFrame({'A': ['1', '2', '3'], 'B': ['4', '5', '6'], 'C': ['7', '8', '9']})

    A   B   C
0   1   4   7
1   2   5   8
2   3   6   9

所有列连接 - 不使用分隔符

cols = ['A', 'B', 'C']
df['combined'] = df[cols].apply(lambda row: ''.join(row.values.astype(str)), axis=1)

    A   B   C   combined
0   1   4   7   147
1   2   5   8   258
2   3   6   9   369

使用'_'分隔符连接两列A和C:

cols = ['A', 'C']
df['combined'] = df[cols].apply(lambda row: '_'.join(row.values.astype(str)), axis=1)

    A   B   C   combined
0   1   4   7   1_7
1   2   5   8   2_8
2   3   6   9   3_9

0
作为对@Gary Dorman评论中问题的解决方案, 我想要放置一个分隔符,这样当您查看整个列时,您就可以看到它是如何分开的。
你可以使用:
df_tmp=df.astype(str) + ','
df_tmp.sum(axis=1).str.rstrip(',')

之前:

1.2.3.480tcp
6.6.6.680udp
7.7.7.78080tcp
8.8.8.88080tcp
9.9.9.98080tcp

之后:

1.2.3.4,80,tcp
6.6.6.6,80,udp
7.7.7.7,8080,tcp
8.8.8.8,8080,tcp
9.9.9.9,8080,tcp

哪个看起来更好(像CSV :) 在我的机器上,这个额外的分隔步骤要慢大约30%。


请注意,以这种方式创建CSV可能会导致无效的格式。例如,在字段中使用分隔符令牌时。 - Ivan De Paz Centeno

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接