如何在Pandas数据框中将多个列的值拼接成单个列?

121

这个问题与先前发布的问题相同。我想要合并三列而不是两列:

以下是合并两列的代码示例:

df = DataFrame({'foo':['a','b','c'], 'bar':[1, 2, 3], 'new':['apple', 'banana', 'pear']})

df['combined']=df.apply(lambda x:'%s_%s' % (x['foo'],x['bar']),axis=1)

df
    bar foo new combined
0   1   a   apple   a_1
1   2   b   banana  b_2
2   3   c   pear    c_3

我想用这个命令合并三列,但是它没有生效,有任何想法吗?

df['combined']=df.apply(lambda x:'%s_%s' % (x['bar'],x['foo'],x['new']),axis=1)

6
如果你想将三列合并,你需要三个 %s (%s_%s_%s)。例如:df['combined']=df.apply(lambda x:'%s_%s_%s' % (x['bar'],x['foo'],x['new']),axis=1) - user2652620
2
可能是 String concatenation of two pandas columns 的重复问题。 - MrFun
2
一个更全面的答案,显示多种方法的时间是在pandas数据框中合并两列文本 - smci
您参考的帖子中有 df.astype(str).agg('_'.join, axis=1) - Ynjxsjmh
15个回答

180

另一个解决方案是使用DataFrame.apply(),键入较少且在想要连接更多列时更具可伸缩性:

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

5
当列列表保存为变量并且每次可以容纳不同数量的列时,这是最佳解决方案。 - M_Idk392845
4
我遇到的小问题是.values.astype(str)None转换为字符串'None',而不是空字符串。显然。 - grofte
10
不使用lambda(更快且更简洁):df[cols].astype(str).apply('_'.join, axis=1)。然而,使用.str.cat(...).str.cat(...)...仍然更快。 - Pierre D

113
您可以使用字符串拼接将列组合起来,带或不带分隔符。您必须将非字符串列的类型转换为字符串。

您可以使用字符串拼接将列组合起来,带或不带分隔符。您必须将非字符串列的类型转换为字符串。

In[17]: df['combined'] = df['bar'].astype(str) + '_' + df['foo'] + '_' + df['new']

In[17]:df
Out[18]: 
   bar foo     new    combined
0    1   a   apple   1_a_apple
1    2   b  banana  2_b_banana
2    3   c    pear    3_c_pear

12
相较于使用.apply(, axis=1)在更大的数据框上,这种解决方案将会快得多。 - MaxU - stand with Ukraine
2
@MaxU 是的,而且非常容易。 - shivsn
2
当我使用这个解决方案时,我会收到一个“SettingWithCopyWarning”警告 - 我该如何避免触发该警告? - Nate
5
当需要联接多列时这就变得很烦人了。 - derchambers
3
如果任何一列是None,则df['combined']变为nan。例如:如果df.new.iloc[0] == None,那么df.combined.iloc[0]将变为nan,而不是1_a_ - Avantika Banerjee
显示剩余2条评论

29

如果您有更多要合并的列,使用Series方法str.cat可能会很方便:

df["combined"] = df["foo"].str.cat(df[["bar", "new"]].astype(str), sep="_")
基本上,您选择第一列(如果它不是str类型,则需要附加.astype(str)),然后将其他列附加到其后(用一个可选的分隔符字符分隔)。

1
聪明的做法,但这给我带来了巨大的内存错误。虽然可能有些繁琐,但编写 df[col].map(str) + '_' df[col2].map(str) + ... + df[col9].map(str) 更加高效。 - Corey Levinson
1
很有趣!我不知道我们可以在 Series.str.cat() 中使用 DataFrame 作为参数。 - MaxU - stand with Ukraine
1
这对我来说是最容易的,而且我喜欢sep参数。 - avirr
没有内存问题。必须添加df["foo"].fillna('') - citynorman

18

我只是想对两种解决方案进行时间比较(对于3万行数据框):

In [1]: df = DataFrame({'foo':['a','b','c'], 'bar':[1, 2, 3], 'new':['apple', 'banana', 'pear']})

In [2]: big = pd.concat([df] * 10**4, ignore_index=True)

In [3]: big.shape
Out[3]: (30000, 3)

In [4]: %timeit big.apply(lambda x:'%s_%s_%s' % (x['bar'],x['foo'],x['new']),axis=1)
1 loop, best of 3: 881 ms per loop

In [5]: %timeit big['bar'].astype(str)+'_'+big['foo']+'_'+big['new']
10 loops, best of 3: 44.2 ms per loop

还有几个更多的选项:

In [6]: %timeit big.ix[:, :-1].astype(str).add('_').sum(axis=1).str.cat(big.new)
10 loops, best of 3: 72.2 ms per loop

In [11]: %timeit big.astype(str).add('_').sum(axis=1).str[:-1]
10 loops, best of 3: 82.3 ms per loop

1
非常好,附加选项。 - shivsn

12

可能最快的解决方法是在纯Python环境下操作:

Series(
    map(
        '_'.join,
        df.values.tolist()
        # when non-string columns are present:
        # df.values.astype(str).tolist()
    ),
    index=df.index
)

与 @MaxU 的回答进行比较(使用同时具有数字和字符串列的 big 数据框):

%timeit big['bar'].astype(str) + '_' + big['foo'] + '_' + big['new']
# 29.4 ms ± 1.08 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)


%timeit Series(map('_'.join, big.values.astype(str).tolist()), index=big.index)
# 27.4 ms ± 2.36 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

对比@derchambers的答案(使用他们的df数据框,其中所有列都是字符串):

from functools import reduce

def reduce_join(df, columns):
    slist = [df[x] for x in columns]
    return reduce(lambda x, y: x + '_' + y, slist[1:], slist[0])

def list_map(df, columns):
    return Series(
        map(
            '_'.join,
            df[columns].values.tolist()
        ),
        index=df.index
    )

%timeit df1 = reduce_join(df, list('1234'))
# 602 ms ± 39 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit df2 = list_map(df, list('1234'))
# 351 ms ± 12.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

9

@allen给出的答案相对通用,但在处理大型数据帧时可能缺乏性能:

使用Reduce会更好:

from functools import reduce

import pandas as pd

# make data
df = pd.DataFrame(index=range(1_000_000))
df['1'] = 'CO'
df['2'] = 'BOB'
df['3'] = '01'
df['4'] = 'BILL'


def reduce_join(df, columns):
    assert len(columns) > 1
    slist = [df[x].astype(str) for x in columns]
    return reduce(lambda x, y: x + '_' + y, slist[1:], slist[0])


def apply_join(df, columns):
    assert len(columns) > 1
    return df[columns].apply(lambda row:'_'.join(row.values.astype(str)), axis=1)

# ensure outputs are equal
df1 = reduce_join(df, list('1234'))
df2 = apply_join(df, list('1234'))
assert df1.equals(df2)

# profile
%timeit df1 = reduce_join(df, list('1234'))  # 733 ms
%timeit df2 = apply_join(df, list('1234'))   # 8.84 s


有没有一种方法可以不丢弃空单元格,而不添加分隔符,例如,要连接的字符串是“”,“a”和“b”,预期结果是“_a_b”,但是可能会有“a_b”。我找不到有效的方法来实现这一点,因为它需要逐行操作,因为每行的长度是不同的。 - Yang
我不确定你的意思 @Yang,或许可以发一篇新问题并提供一个可行的代码示例? - derchambers

8

我认为您遗漏了一个%s

df['combined']=df.apply(lambda x:'%s_%s_%s' % (x['bar'],x['foo'],x['new']),axis=1)

7

首先将列转换为字符串。然后使用.T.agg('_'.join)函数连接它们。更多信息可以在这里获得。

# Initialize columns
cols_concat = ['first_name', 'second_name']

# Convert them to type str
df[cols_concat] = df[cols_concat].astype('str')

# Then concatenate them as follows
df['new_col'] = df[cols_concat].T.agg('_'.join)

很好的评论。但是你在最后使用transpose是必要的吗?它和.agg(''.join, axis=1)是一样的吗?谢谢。 - undefined

3
如果您有一列需要连接的列表,可能还想使用一些分隔符,那么您可以这样做:
def concat_columns(df, cols_to_concat, new_col_name, sep=" "):
    df[new_col_name] = df[cols_to_concat[0]]
    for col in cols_to_concat[1:]:
        df[new_col_name] = df[new_col_name].astype(str) + sep + df[col].astype(str)

这种方法比使用apply更快,并且可以连接任意数量的列。


2
df = DataFrame({'foo':['a','b','c'], 'bar':[1, 2, 3], 'new':['apple', 'banana', 'pear']})

df['combined'] = df['foo'].astype(str)+'_'+df['bar'].astype(str)

如果您想要使用字符串(“_”)进行连接,请先将您需要的列转换为字符串,然后再将数据框进行连接。请注意保留HTML标签。

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