遍历行并展开Pandas数据框

7
我有一个Pandas数据帧,其中包含一个列,该列包含值或值列表(长度不等)。 我想要“展开”行,因此列表中的每个值都成为列中的单个值。 一个示例可以说明一切:
dfIn = pd.DataFrame({u'name': ['Tom', 'Jim', 'Claus'],
 u'location': ['Amsterdam', ['Berlin','Paris'], ['Antwerp','Barcelona','Pisa'] ]})

    location     name
0   Amsterdam   Tom
1   [Berlin, Paris] Jim
2   [Antwerp, Barcelona, Pisa]  Claus

我想变成:

dfOut = pd.DataFrame({u'name': ['Tom', 'Jim', 'Jim', 'Claus','Claus','Claus'],
u'location': ['Amsterdam', 'Berlin','Paris', 'Antwerp','Barcelona','Pisa']})

    location     name
0   Amsterdam   Tom
1   Berlin   Jim
2   Paris   Jim
3   Antwerp Claus
4   Barcelona   Claus
5   Pisa    Claus

我最初尝试使用apply,但据我所知,不可能返回多个Series。iterrows似乎是一个技巧。但是下面的代码给了我一个空的数据框...

def duplicator(series):
    if type(series['location']) == list:
        for location in series['location']:
            subSeries = series
            subSeries['location'] = location
            dfOut.append(subSeries)
    else:
        dfOut.append(series)

for index, row in dfIn.iterrows():
    duplicator(row)
3个回答

9

虽然没有太多有趣/花哨的熊猫用法,但这个方法可行:

import numpy as np
dfIn.loc[:, 'location'] = dfIn.location.apply(np.atleast_1d)
all_locations = np.hstack(dfIn.location)
all_names = np.hstack([[n]*len(l) for n, l in dfIn[['name', 'location']].values])
dfOut = pd.DataFrame({'location':all_locations, 'name':all_names})

这个方法比apply/stack/reindex方法快大约40倍。据我所知,在几乎所有的数据框大小上,这个比例都是成立的(没有测试每行列表大小对它的影响)。如果你能保证所有的location条目已经是可迭代的,你可以删除atleast_1d调用,这将再提高约20%的速度。


这个解决方案更加优雅。 - Richard Mao

5
如果您返回的系列数据的索引是位置列表,那么dfIn.apply将把这些系列数据汇总到一个表中:
import pandas as pd
dfIn = pd.DataFrame({u'name': ['Tom', 'Jim', 'Claus'],
                     u'location': ['Amsterdam', ['Berlin','Paris'],
                                   ['Antwerp','Barcelona','Pisa'] ]})

def expand(row):
    locations = row['location'] if isinstance(row['location'], list) else [row['location']]
    s = pd.Series(row['name'], index=list(set(locations)))
    return s

In [156]: dfIn.apply(expand, axis=1)
Out[156]: 
  Amsterdam Antwerp Barcelona Berlin Paris   Pisa
0       Tom     NaN       NaN    NaN   NaN    NaN
1       NaN     NaN       NaN    Jim   Jim    NaN
2       NaN   Claus     Claus    NaN   NaN  Claus

你可以将此DataFrame堆叠以获得:
In [157]: dfIn.apply(expand, axis=1).stack()
Out[157]: 
0  Amsterdam      Tom
1  Berlin         Jim
   Paris          Jim
2  Antwerp      Claus
   Barcelona    Claus
   Pisa         Claus
dtype: object

这是一个Series,而你想要一个DataFrame。使用reset_index稍加调整即可获得所需结果:

dfOut = dfIn.apply(expand, axis=1).stack()
dfOut = dfOut.to_frame().reset_index(level=1, drop=False)
dfOut.columns = ['location', 'name']
dfOut.reset_index(drop=True, inplace=True)
print(dfOut)

产量
    location   name
0  Amsterdam    Tom
1     Berlin    Jim
2      Paris    Jim
3  Amsterdam  Claus
4    Antwerp  Claus
5  Barcelona  Claus

我遇到了一个错误,可能是由于奇怪的数据引起的(这可能是由于某些列表中存在空值引起的吗?):InvalidIndexError: 重新索引仅适用于具有唯一值的索引对象。 - bowlby
这可能是因为您的位置列表之一包含重复项。您想如何处理重复位置(对于同一个人)? - unutbu
重复的值应该被“合并”(例如,结果中只应该保留1个),空值应该被忽略。 - bowlby
我已将 index=locations 改为 index=list(set(locations)),这将移除重复项。 - unutbu

1
import pandas as pd


dfIn = pd.DataFrame({
    u'name': ['Tom', 'Jim', 'Claus'],
    u'location': ['Amsterdam', ['Berlin','Paris'], ['Antwerp','Barcelona','Pisa'] ],
})

print(dfIn.explode('location'))

>>>
    name   location
0    Tom  Amsterdam
1    Jim     Berlin
1    Jim      Paris
2  Claus    Antwerp
2  Claus  Barcelona
2  Claus       Pisa

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