在 Python Pandas 中以长格式附加列表元素
Posted
技术标签:
【中文标题】在 Python Pandas 中以长格式附加列表元素【英文标题】:Append list elements over long format in Python Pandas 【发布时间】:2018-09-23 03:35:18 【问题描述】:我有以下数据:
study_id list_value
1 ['aaa', 'bbb']
1 ['aaa']
1 ['ccc']
2 ['ddd', 'eee', 'aaa']
2 np.NaN
2 ['zzz', 'aaa', 'bbb']
我怎样才能把它变成这样的东西?
study_id list_value
1 ['aaa', 'bbb', 'ccc']
1 ['aaa', 'bbb', 'ccc']
1 ['aaa', 'bbb', 'ccc']
2 ['aaa', 'bbb', 'ddd', 'eee', 'zzz']
2 ['aaa', 'bbb', 'ddd', 'eee', 'zzz']
2 ['aaa', 'bbb', 'ddd', 'eee', 'zzz'] # order of list item doesn't matter
【问题讨论】:
第五行的缺失值是None
、np.nan
、''
还是[]
?
【参考方案1】:
itertools.chain
与 GroupBy.transform
首先,使用列表推导去除列中的 NaN(我知道这很麻烦,但这是最快的方法)。
df['list_value'] = [
[] if not isinstance(x, list) else x for x in df.list_value
]
接下来,在study_id
上进行分组并将GroupBy.transform
中的列表展平,并使用set
提取唯一值。
from itertools import chain
df['list_value'] = df.groupby('study_id').list_value.transform(
lambda x: [list(set(chain.from_iterable(x)))]
)
作为最后一步,如果您打算改变单个列表项,您可能需要这样做
df['list_value'] = [x[:] for x in df['list_value']]
如果没有,一个列表中的更改将反映在该组中的所有子列表中。
df
study_id list_value
0 1 [aaa, ccc, bbb]
1 1 [aaa, ccc, bbb]
2 1 [aaa, ccc, bbb]
3 2 [bbb, ddd, eee, aaa, zzz]
4 2 [bbb, ddd, eee, aaa, zzz]
5 2 [bbb, ddd, eee, aaa, zzz]
【讨论】:
永远忘记itertools.chain
:-(【参考方案2】:
defaultdict
from collections import defaultdict
d = defaultdict(set)
for t in df.dropna(subset=['list_value']).itertuples():
d[t.study_id] |= set(t.list_value)
df.assign(list_value=df.study_id.map(pd.Series(d).apply(sorted)))
study_id list_value
0 1 [a, b, c]
1 1 [a, b, c]
2 1 [a, b, c]
3 2 [a, b, d, e, z]
4 2 [a, b, d, e, z]
5 2 [a, b, d, e, z]
np.unique
和其他技巧
请注意,结果是ndarray
df.assign(
list_value=df.study_id.map(
df.set_index('study_id').list_value.dropna().sum(level=0).apply(np.unique)
)
)
study_id list_value
0 1 [a, b, c]
1 1 [a, b, c]
2 1 [a, b, c]
3 2 [a, b, d, e, z]
4 2 [a, b, d, e, z]
5 2 [a, b, d, e, z]
我们需要使用sorted
才能到达那里
df.assign(
list_value=df.study_id.map(
df.set_index('study_id').list_value.dropna()
.sum(level=0).apply(np.unique).apply(sorted)
)
)
粗制滥造!
df.assign(
list_value=df.study_id.map(
df.list_value.str.join('|').groupby(df.study_id).apply(
lambda x: sorted(set('|'.join(x.dropna()).split('|')))
)
)
)
study_id list_value
0 1 [a, b, c]
1 1 [a, b, c]
2 1 [a, b, c]
3 2 [a, b, d, e, z]
4 2 [a, b, d, e, z]
5 2 [a, b, d, e, z]
设置
df = pd.DataFrame(dict(
study_id=[1, 1, 1, 2, 2, 2],
list_value=[['a', 'b'], ['a'], ['c'], ['d', 'e', 'a'], np.nan, ['z', 'a', 'b']]
), columns=['study_id', 'list_value'])
【讨论】:
【参考方案3】:这是一种手动方式。
import pandas as pd, numpy as np
from itertools import chain
df = pd.DataFrame('study_id': [1, 1, 1, 2, 2, 2],
'list_value': [['aaa', 'bbb',], ['aaa'], ['ccc'],['ddd', 'eee', 'aaa'],
np.nan, ['zzz', 'aaa', 'bbb']])
counts = df['study_id'].value_counts()
grp = df.dropna(subset=['list_value'])\
.groupby('study_id')['list_value']\
.apply(lambda x: sorted(set(chain.from_iterable(x))))\
.reset_index()
res = pd.concat([pd.concat([grp[grp['study_id'] == x]]*counts[x]) for x in counts.index])\
.sort_values('study_id')\
.reset_index(drop=True)
# study_id list_value
# 0 1 [aaa, bbb, ccc]
# 1 1 [aaa, bbb, ccc]
# 2 1 [aaa, bbb, ccc]
# 3 2 [aaa, bbb, ddd, eee, zzz]
# 4 2 [aaa, bbb, ddd, eee, zzz]
# 5 2 [aaa, bbb, ddd, eee, zzz]
【讨论】:
有点风,但还是不错的 +1【参考方案4】:用空列表填充你的空值,然后使用transform
df.at[df.list_value.isnull().nonzero()[0][0],'list_value']=[]
df.groupby('study_id').list_value.transform(lambda x : [list(set(x.sum()))])
Out[160]:
0 [b, c, a]
1 [b, c, a]
2 [b, c, a]
3 [b, e, d, z, a]
4 [b, e, d, z, a]
5 [b, e, d, z, a]
Name: list_value, dtype: object
【讨论】:
x.sum() 是二次的 ;-)以上是关于在 Python Pandas 中以长格式附加列表元素的主要内容,如果未能解决你的问题,请参考以下文章
Python Pandas Dataframe:如何同时将多个索引附加到列表中?
如何以附加模式导出 DataFrame to_json - Python Pandas?