将数据框中的列表元素拆分为多行

时间:2019-04-24 12:02:04

标签: python pandas dataframe

我有一个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的情况?

2 个答案:

答案 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

使用pd.set_index

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)