合并两个包含列表的数据框列,使得列表的顺序保持不变

3
我将尝试合并/连接两列文本,这两列都包含相关但分开的文本数据,用 "|" 分隔,另外还需要将某些名称替换为 "" 并将 | 替换为 '\n'。
例如,原始数据可能是:
    First Names            Last Names
0   Jim|James|Tim          Simth|Jacobs|Turner
1   Mickey|Mini            Mouse|Mouse
2   Mike|Billy|Natasha     Mills|McGill|Tsaka

如果我想合并/连接以得到完整的姓名,并删除与“Smith”相关的条目,则最终的数据框应该如下所示:
    First Names            Last Names            Full Names
0   Jim|James|Tim          Simth|Jacobs|Turner   James Jacobs\nTim Turner
1   Mickey|Mini            Mouse|Mouse           Mickey Mouse\nMini Mouse
2   Mike|Billy|Natasha     Mills|McGill|Tsaka    Mike Mills\nBilly McGill\nNatasha Tsaka

目前我的做法是:

def parse_merge(df, col1, col2, splitter, new_col, list_to_exclude):

    orig_order = pd.Series(list(df.index)).rename('index')

    col1_df = pd.concat([orig_order, df[col1], df[col1].str.split(splitter, expand=True)], axis = 1)
    col2_df = pd.concat([orig_order, df[col2], df[col2].str.split(splitter, expand=True)], axis = 1)

    col1_melt = pd.melt(col1_df, id_vars=['index', col1], var_name='count')
    col2_melt = pd.melt(col2_df, id_vars=['index', col2], var_name='count')

    col2_melt['value'] = '(' + col2_melt['value'].astype(str) + ')'
    col2_melt = col2_melt.rename(columns={'value':'value2'})

    melted_merge = pd.concat([col1_melt, col2_melt['value2']], axis = 1 )

    if len(list_to_exclude) > 0:
         list_map = map(re.escape, list_to_exclude)

    melted_merge.ix[melted_merge['value2'].str.contains('|'.join(list_map)), ['value', 'value2']] = ''

    melted_merge[new_col] = melted_merge['value'] + " " + melted_merge['value2']

如果我调用:

parse_merge(names, 'First Names', 'Last Names', 'Full Names', ['Smith'])

数据变成:

    Index   First Names        count    value            value2        Full Names
0   0       Jim|James|Tim      0        Jim              Smith         ''
1   1       Mickey|Mini        0        Mickey           Mouse         Mickey Mouse
2   2       Mike|Billy|Natasha 0        Mike             Mills         Mike Mills

我不确定如何在没有循环的情况下完成这个任务,或者是否有更有效/完全不同的方法。

感谢所有的建议!

3个回答

4

以下是使用 pd.DataFrame.apply 和一些 Python 内置特性的简洁解决方案:

def combine_names(row):

    pairs = list(zip(row[0].split('|'), row[1].split('|')))
    return '\n'.join([' '.join(p) for p in pairs if p[1] != 'Simth'])

df['Full Name'] = df.apply(combine_names, axis=1)

很好的解决方案 @AlexG - nipy
感谢您提供的解决方案!如果我想将此方法推广到包含n列的数据框中,而我只想添加一个由两个合并列组成的列,应该怎么做?另外,我对.apply不太熟悉 - 我可以将参数传递给combine_names吗?最好能够传递要跳过的名称列表,而不仅仅是'Smith'这个示例。 - wingsoficarus116
您可以直接将姓名列表编码到 combine_names 函数中,但它只能接受一个参数。传递的参数是行(假设轴设置为1)。如果您有超过这两列,则还可以像这样调用它:df[['First Names', 'Last Names']].apply(combine_names, axis=1)。回到您的第一个观点,您可以将 if p[1] != 'Simth' 更改为类似于 if p[1] not in ['Simth', 'John', 'King'] 的内容。 - Alex
我通过将combine_names函数嵌入另一个函数中来解决了这个问题,该函数创建了一个单独的数据框供combine_names函数使用,然后将其连接到原始数据框。非常感谢,非常聪明! - wingsoficarus116

3

我非常喜欢@AlexG的解决方案 - 请使用它。

这是我尝试创建一个创意一行代码的解决方案 - 它非常奇怪,所以不应该被使用 - 只是为了好玩:

In [78]: df
Out[78]:
          First Names           Last Names
0       Jim|James|Tim  Simth|Jacobs|Turner
1         Mickey|Mini          Mouse|Mouse
2  Mike|Billy|Natasha   Mills|McGill|Tsaka

In [79]: df['Full Names'] = \
    ...: (df.stack()
    ...:    .str.split(r'\|', expand=True)
    ...:    .unstack(level=1)
    ...:    .groupby(level=0, axis=1)
    ...:    .apply(lambda x: x.add(' ').sum(axis=1).str.strip())
    ...:    .replace([r'\w+\s+Simth'], [np.nan], regex=True)
    ...:    .apply(lambda x: x.dropna().str.cat(sep='\n'), axis=1)
    ...: )
    ...:

In [80]: df
Out[80]:
          First Names           Last Names                               Full Names
0       Jim|James|Tim  Simth|Jacobs|Turner                 James Jacobs\nTim Turner
1         Mickey|Mini          Mouse|Mouse                 Mickey Mouse\nMini Mouse
2  Mike|Billy|Natasha   Mills|McGill|Tsaka  Mike Mills\nBilly McGill\nNatasha Tsaka

2
我有很多理解力。
需要翻译的内容已经翻译完毕。
l = df.values.tolist()

['|'.join(n)
 for n in [[' '.join(z)
 for z in zip(*[s.split('|')
 for s in r]) if z[1] != 'Smith']
 for r in l]]

['James Jacobs|Tim Turner',
 'Mickey Mouse|Mini Mouse',
 'Mike Mills|Billy McGill|Natasha Tsaka']

l = df.values.tolist()

df['Full Names'] = [
     '|'.join(n)
     for n in [[' '.join(z)
     for z in zip(*[s.split('|')
     for s in r]) if z[1] != 'Smith']
     for r in l]]

df

在此输入图片描述


撇开文字游戏,这在示例数据上非常迅速。

在此输入图片描述


更详细的解释

l

[['Jim|James|Tim', 'Simth|Jacobs|Turner'],
 ['Mickey|Mini', 'Mouse|Mouse'],
 ['Mike|Billy|Natasha', 'Mills|McGill|Tsaka']]
  • l 是一个包含多个列表的列表。我将广泛使用列表推导式和可迭代对象。
  • 每个子列表都由2个字符串组成,我将对它们进行拆分并进行zip操作。
  • 拆分的结果将是一个由(first, last)名字组成的元组列表。我将使用if z[1] != 'Smith'来过滤掉姓Smith的人。
    • 顺便说一句,在这一行中,您可以使用z[1] not in list_of_names
  • 然后我将使用' '.join(实际上是一个函数)来将每个元组组合成first last
  • 然后我将使用另一个'|'.join来将first last的子列表组合成first1 last1|first2 last2...以此类推

之所以这样更快,是因为推导式已经被优化到了极致。其他解决方案使用的是apply,这是一种通用的循环结构,只有在特定情况下才能利用快速循环(如果有人知道更多,请纠正我)。使用lambda绝对不是这些情况之一。


你能解释一下这个程序为什么如此高效以及它具体在做什么吗?我大致能理解第一个答案(AlexG的“combin_names”方法),但这已经超出了我的能力范围。对于我的有限知识,我表示歉意。 - wingsoficarus116
@wingsoficarus116 更新了一些类似于解释的内容。 - piRSquared

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