如何有效地扩展/展平pandas数据框

时间:2017-02-10 20:26:56

标签: python pandas

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

我设法用iterrowsdictappend(见下文)解决了这个问题,但是我的真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

3 个答案:

答案 0 :(得分:4)

您可以使用numpy来获得更好的效果:

两种解决方案主要使用numpy.repeat

from  itertools import chain

vals = df.fields.str.len()
df1 = pd.DataFrame({
        "id": np.repeat(df.id.values,vals),
        "name": np.repeat(df.name.values, vals),
        "fields": list(chain.from_iterable(df.fields))})
df1 = df1.reindex_axis(df.columns, axis=1)
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

另一种解决方案:

df[['id','name']].values将列转换为numpy array并按numpy.repeat复制,然后将lists中的值堆叠为numpy.hstack,并按numpy.column_stack添加

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

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

更一般的解决方案是过滤掉列fields,然后将其添加到DataFrame构造函数中,因为始终是最后一列:

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

答案 1 :(得分:2)

如果您的CSV长数千行,则using_string_methods(下方) 可能比using_iterrowsusing_repeat更快:

csv = 'id|name|fields'+("""
1|abc|[qq,ww,rr]
2|efg|[zz,xx,rr]"""*10000)

In [210]: %timeit using_string_methods(csv)
10 loops, best of 3: 100 ms per loop

In [211]: %timeit using_itertuples(csv)
10 loops, best of 3: 119 ms per loop

In [212]: %timeit using_repeat(csv)
10 loops, best of 3: 126 ms per loop

In [213]: %timeit using_iterrows(csv)
1 loop, best of 3: 1min 7s per loop

因此,对于10000行CSV,using_string_methodsusing_iterrows快600倍,并且比using_repeat略快。

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)

通常,仅当数据位于a中时,才可能进行快速NumPy / Pandas操作 原生NumPy dtype(例如int64float64或字符串。)一旦放置 数据框中的列表(非本地NumPy dtype)夹具已启动 - 您被迫使用 使用Python-speed循环来处理列表。

因此,为了提高性能,您需要避免将列表放在DataFrame中。

using_string_methodsfields数据加载为字符串:

df = pd.read_csv(StringIO(csv), sep='|', dtype=None)

并避免使用apply方法(通常与普通的Python循环一样慢):

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进行重新整形 将DataFrame转换为所需的格式。

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

顺便说一句,您可能有兴趣看到稍加修改using_iterrows可以与using_repeat一样快。我在using_itertuples中显示了更改。 df.itertuples往往比df.iterrows略快,但差别很小。大多数速度增益是通过避免在{for循环中调用df.append来实现的。{/ 3}}。

答案 2 :(得分:1)

您可以将fields列中的列表分为多个列,方法是将pandas.Series应用于fields,然后合并到idname,如下所示:

cols = df.columns[df.columns != 'fields'].tolist() # adapted from @jezrael 
df = df[cols].join(df.fields.apply(pandas.Series))

然后,您可以使用set_indexstack来融合生成的新列,然后重置索引:

df = df.set_index(cols).stack().reset_index()

最后,删除reset_index生成的冗余列,并将生成的列重命名为" field":

df = df.drop(df.columns[-2], axis=1).rename(columns={0: 'field'})