我有一个df。
df=pd.DataFrame(data=[[301,301,302,303],[['a'],['b','c'],['e','f',33,'Z'],42],index=['id','foo']).T
我想转到第二个数据帧,在foo
中仅包含标量值。
当且仅当原始值是一个列表时,我想将其分布在多个新行中,其他值重复。
例如来自:
id foo
0 301 [a]
1 301 [b, c]
2 302 [e, f,33,'Z']
3 303 42
收件人:
id foo
0 301 a
1 301 b
1 301 c
2 302 e
2 302 f
2 302 33
2 302 Z
3 303 42
在Split set values from Pandas dataframe cell over multiple rows中,
我已经学会了如何对一列执行此操作,但是如何处理df有多个列需要复制为id
的情况?
答案 0 :(得分:1)
df.set_index('id')['foo'].apply(pd.Series).stack().reset_index(name='foo').drop('level_1', axis=1)
输出
id foo
0 301 a
1 301 b
2 301 c
3 302 e
4 302 f
5 302 33
6 302 Z
7 303 42
多列方案
id test foo
0 301 1 [a]
1 301 2 [b, c]
2 302 3 [e, f, 33, Z]
3 303 4 42
df.set_index(['id','test'])['foo'].apply(pd.Series).stack().reset_index(name='foo').drop('level_2', axis=1)
输出
id test foo
0 301 1 a
1 301 2 b
2 301 2 c
3 302 3 e
4 302 3 f
5 302 3 33
6 302 3 Z
7 303 4 42
答案 1 :(得分:1)
如果要避免使用apply(pd.Series)
因为slow,这是另一种解决方案-首先将非列表值转换为一个元素列表,然后应用解决方案:
df['foo'] = [x if isinstance(x, list) else [x] for x in df['foo']]
from itertools import chain
df = pd.DataFrame({
'id' : df['id'].values.repeat(df['foo'].str.len()),
'foo' : list(chain.from_iterable(df['foo'].tolist()))
})
或者:
L = [x if isinstance(x, list) else [x] for x in df['foo']]
from itertools import chain
df = pd.DataFrame({
'id' : df['id'].values.repeat([len(x) for x in L]),
'foo' : list(chain.from_iterable(L))
})
print (df)
id foo
0 301 a
1 301 b
2 301 c
3 302 e
4 302 f
5 302 33
6 302 Z
7 303 42
如果小数据或性能不重要-用pop
提取列foo
的解决方案:
s = df.pop('foo').apply(pd.Series).stack().reset_index(level=1, drop=True).rename('foo')
df = df.join(s).reset_index(drop=True)
或使用drop
解决方案:
s = df['foo'].apply(pd.Series).stack().reset_index(level=1, drop=True).rename('foo')
df = df.drop('foo', axis=1).join(s).reset_index(drop=True)
print (df)
id foo
0 301 a
1 301 b
2 301 c
3 302 e
4 302 f
5 302 33
6 302 Z
7 303 42
df=pd.DataFrame(data=[[301,301,302,303],[['a'],['b','c'],['e','f',33,'Z'],42]],index=['id','foo']).T
df = pd.concat([df] * 1000, ignore_index=True)
def f(df):
s = df['foo'].apply(pd.Series).stack().reset_index(level=1, drop=True).rename('foo')
return df.drop('foo', axis=1).join(s).reset_index(drop=True)
In [241]: %timeit (f(df))
814 ms ± 11.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [242]: %%timeit
...: L = [x if isinstance(x, list) else [x] for x in df['foo']]
...:
...: from itertools import chain
...:
...: pd.DataFrame({
...: 'id' : df['id'].values.repeat([len(x) for x in L]),
...: 'foo' : list(chain.from_iterable(L))
...:
...: })
...:
2.6 ms ± 15.6 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)