Python 如何有效地扩展/展平数据帧

Python 如何有效地扩展/展平数据帧,python,pandas,Python,Pandas,我有一个数据集,在它的一列上,每个元素都是一个列表。 我想把它展平,这样每个列表元素都有自己的一行 我设法用iterrows、dict和append(见下文)解决了这个问题,但我的真实DF太大,速度太慢。 有没有办法让事情变得更快 我可以考虑用另一种格式(可能是层次的DF)替换每个元素的列(如果是分层的DF),如果这更合理的话。 编辑:我有许多专栏,其中一些可能会在将来更改。我唯一确定的是我有字段栏。这就是为什么我在解决方案中使用dict 一个最简单的示例,创建要使用的df: import St

我有一个数据集,在它的一列上,每个元素都是一个列表。 我想把它展平,这样每个列表元素都有自己的一行

我设法用
iterrows
dict
append
(见下文)解决了这个问题,但我的真实DF太大,速度太慢。 有没有办法让事情变得更快

我可以考虑用另一种格式(可能是层次的DF)替换每个元素的列(如果是分层的DF),如果这更合理的话。 编辑:我有许多专栏,其中一些可能会在将来更改。我唯一确定的是我有字段栏。这就是为什么我在解决方案中使用

dict

一个最简单的示例,创建要使用的df:

import StringIO
df = pd.read_csv(StringIO.StringIO("""
id|name|fields
1|abc|[qq,ww,rr]
2|efg|[zz,xx,rr]
"""), sep='|')
df.fields = df.fields.apply(lambda s: s[1:-1].split(','))
print df
结果df:

   id name        fields
0   1  abc  [qq, ww, rr]
1   2  efg  [zz, xx, rr]
我的(慢)解决方案:

new_df = pd.DataFrame(index=[], columns=df.columns)

for _, i in df.iterrows():
    flattened_d = [dict(i.to_dict(), fields=c) for c in i.fields]
    new_df = new_df.append(flattened_d )
导致

    id name fields
0  1.0  abc     qq
1  1.0  abc     ww
2  1.0  abc     rr
0  2.0  efg     zz
1  2.0  efg     xx
2  2.0  efg     rr

通过将
pandas.Series
应用于
字段,然后合并到
id
name
,可以将
字段中的列表拆分为多列,如下所示:

cols = df.columns[df.columns != 'fields'].tolist() # adapted from @jezrael 
df = df[cols].join(df.fields.apply(pandas.Series))
然后,您可以使用
set_index
stack
融化生成的新列,然后重置索引:

df = df.set_index(cols).stack().reset_index()
最后,删除reset_index生成的冗余列,并将生成的列重命名为“field”:


您可以使用
numpy
以获得更好的性能:

这两种解决方案主要使用

另一个解决方案:

new_df = pd.DataFrame(index=[], columns=df.columns)

for _, i in df.iterrows():
    flattened_d = [dict(i.to_dict(), fields=c) for c in i.fields]
    new_df = new_df.append(flattened_d )
df[['id','name']]。值
将列转换为
numpy数组
并按进行复制,然后将值堆叠在
列表中
按进行添加

更通用的解决方案是过滤掉列
字段
,然后将其添加到
数据帧
构造函数中,因为总是最后一列:

cols = df.columns[df.columns != 'fields'].tolist()
print (cols)
['id', 'name']

df1 = pd.DataFrame(np.column_stack((df[cols].values.
                   repeat(list(map(len,df.fields)),axis=0),np.hstack(df.fields))), 
                   columns=cols + ['fields'])

print (df1)
  id name fields
0  1  abc     qq
1  1  abc     ww
2  1  abc     rr
3  2  efg     zz
4  2  efg     xx
5  2  efg     rr

如果您的CSV有数千行,则使用字符串方法
(如下)
可能比使用\u iterows
或使用\u repeat
的速度更快:

因此,对于10000行CSV,使用字符串方法的
比使用iterrows的
快600倍以上,比使用重复的
快一点



通常,只有当数据处于同一位置时,才能进行快速NumPy/Pandas操作 本地NumPy数据类型(例如
int64
float64
或字符串) 在jig启动的数据帧中列出(非本机NumPy数据类型)——您是被迫的 使用Python速度循环来处理列表

因此,为了提高性能,您需要避免将列表放置在数据帧中

使用_string_方法
字段
数据作为字符串加载:

df = pd.read_csv(StringIO(csv), sep='|', dtype=None)
并避免使用
apply
方法(通常与普通Python循环一样慢):

相反,它使用更快的矢量化字符串方法将字符串分解为 单独列:

fields = (df['fields'].str.extract(r'\[(.*)\]', expand=False)
          .str.split(r',', expand=True))
一旦将字段放在单独的列中,就可以使用
pd.melt
重塑形状 将数据帧转换为所需格式

pd.melt(df, id_vars=['id', 'name'], value_name='field')

顺便说一句,您可能会感兴趣地看到,只需稍微修改一下
,使用\u iterrows
就可以和使用\u repeat
一样快。我使用_itertuples
显示
中的更改。

df.itertuples
往往略快于
df.iterrows
,但差别很小。大部分速度增益是通过避免在for循环中调用
df.append
实现的,因为第一个命令失败。错误是
MergeError:No common columns to execute on
Yep抱歉,我的意思是使用
join
,它根据索引值工作。我更正了我的答案,仍然不起作用。下面是结果(展平为一行):
id name level_2 0 0 1 abc fields[qq,ww,rr]1 2 efg fields[zz,xx,rr]
此外,它看起来像是最后的
reset_index
导致了一个无关的
level_2
列,可以简单地删除(即
df.drop('level_2',axis=1,inplace=True)
)但这并不能解决主要问题,即DF不是扩展的Hanks。我有很多列,有些列将来可能会更改。我唯一能确定的是我有fields列。有没有办法重构您的解决方案,s.t。我不必手动键入“id”、“name”?这就是为什么我在解决方案中使用dict()是的,我认为第二种解决方案更好。给我一分钟。它工作起来很快。你能在正文中解释一下构造函数的输入吗?注意列表是多余的。map(len,df.fields)已经返回了一个列表抱歉,这对于python 3是必需的,在python 2中您可以省略它。谢谢。我喜欢您的方法,但是在我的例子中,原始数据实际上不是来自CSV,所以这不是问题。
import pandas as pd
try: from cStringIO import StringIO         # for Python2
except ImportError: from io import StringIO # for Python3

def using_string_methods(csv):
    df = pd.read_csv(StringIO(csv), sep='|', dtype=None)
    other_columns = df.columns.difference(['fields']).tolist()
    fields = (df['fields'].str.extract(r'\[(.*)\]', expand=False)
              .str.split(r',', expand=True))
    df = pd.concat([df.drop('fields', axis=1), fields], axis=1)
    result = (pd.melt(df, id_vars=other_columns, value_name='field')
              .drop('variable', axis=1))
    result = result.dropna(subset=['field'])
    return result


def using_iterrows(csv):
    df = pd.read_csv(StringIO(csv), sep='|')
    df.fields = df.fields.apply(lambda s: s[1:-1].split(','))
    new_df = pd.DataFrame(index=[], columns=df.columns)

    for _, i in df.iterrows():
        flattened_d = [dict(i.to_dict(), fields=c) for c in i.fields]
        new_df = new_df.append(flattened_d )
    return new_df

def using_repeat(csv):
    df = pd.read_csv(StringIO(csv), sep='|')
    df.fields = df.fields.apply(lambda s: s[1:-1].split(','))
    cols = df.columns[df.columns != 'fields'].tolist()
    df1 = pd.DataFrame(np.column_stack(
        (df[cols].values.repeat(list(map(len,df.fields)),axis=0),
         np.hstack(df.fields))), columns=cols + ['fields'])
    return df1

def using_itertuples(csv):
    df = pd.read_csv(StringIO(csv), sep='|')
    df.fields = df.fields.apply(lambda s: s[1:-1].split(','))
    other_columns = df.columns.difference(['fields']).tolist()
    data = []
    for tup in df.itertuples():
        data.extend([[getattr(tup, col) for col in other_columns]+[field] 
                     for field in tup.fields])
    return pd.DataFrame(data, columns=other_columns+['field'])

csv = 'id|name|fields'+("""
1|abc|[qq,ww,rr]
2|efg|[zz,xx,rr]"""*10000)
df = pd.read_csv(StringIO(csv), sep='|', dtype=None)
df.fields = df.fields.apply(lambda s: s[1:-1].split(','))
fields = (df['fields'].str.extract(r'\[(.*)\]', expand=False)
          .str.split(r',', expand=True))
pd.melt(df, id_vars=['id', 'name'], value_name='field')