【问题标题】:How to deal with pandas column that has a list of dicts in every cell如何处理每个单元格中都有一个字典列表的熊猫列
【发布时间】:2015-04-23 16:36:13
【问题描述】:

我有一个 DataFrame,其中包含一个列,其中每个单元格都由一个 dicts 列表组成,每个 dicts 列表的长度不同(包括 0)。

一个例子:

df = pd.DataFrame({'ID' : [13423,294847,322844,429847], 'RANKS': [[{u'name': u'A', u'price': u'$1.00', u'rank': u'1'},
{u'name': u'B', u'price': u'$4.00', u'rank': u'2'},
{u'name': u'C', u'price': u'$3.99', u'rank': u'3'},
{u'name': u'D', u'price': u'$2.00', u'rank': u'4'},
{u'name': u'E', u'price': u'$2.50', u'rank': u'5'}],

[{u'name': u'AA', u'price': u'$1.99', u'rank': u'1'},
{u'name': u'BB', u'price': u'$6.99', u'rank': u'2'}],

[{u'name': u'Z', u'price': u'$0.99', u'rank': u'1'},
{u'name': u'Y', u'price': u'$10.00', u'rank': u'2'},
{u'name': u'X', u'price': u'$1.99', u'rank': u'3'}],[]], 'count' : [5,2,3,0]})

请注意,'count' 是 'RANKS' 中的字典数。我想到的目标是创建一系列额外的数据框/表(每个“等级”一个)并将它们链接到 HDFStore 中的主表。比如:

Rank_2
ID       Price   Name
13423    $4.00    B  
294847   $6.99    BB 
322844   $10.99   Y 
429847   NaN      NaN   


Rank_3
ID       Price   Name
13423    $3.99    C  
294847   NaN      NaN 
322844   $1.99    X 
429847   NaN      NaN   

这样,如果需要,我可以轻松查询 ID 和排名,但主表不会因为这些分层数据的展开而变得混乱。

但是,问题是我无法弄清楚如何从该列创建 DataFrame。我已经尝试了很多东西,第一个(如果它有效则嵌套在 for 循环中,但当然它没有):

Rank_1 = pd.DataFrame(df.loc[df['count'] > 0]['RANKS'].map(lambda x: pd.DataFrame(x[0])))

其次,因为价格对我来说是最重要的部分:

for i in range(0,5):
    df['rank_%s' % str(i+1)] = df[df['count'] > i]['RANKS'].map(lambda x: x[i]['price'].strip('$'))

然后转换为浮点数。这可行,但这是一个很大的妥协。有没有一种有效的方法(不会挂在 NaN 上)来实现我为每个等级单独 DataFrames 的目标?

【问题讨论】:

  • James,您应该考虑将您的帖子分成两个问题。一种用于将您的数据帧拆分为多个数据帧并用于 HDFStore 问题。
  • 会的。谢谢你,鲍勃。
  • 我的直觉反应是你可能将你的DataFrame分解成许多更小的DataFrame。处理大量小型 DataFrame 需要一个 Python 循环,这通常是通往缓慢之路的一步。相反,我认为使用一个 DataFrame 可能会更好地为您服务,它会使 dicts 列表变平,以便每个内部 dict 在 DataFrame 中都有自己的行。内部字典的键将成为新列。我怀疑这种单一的平面 DataFrame 格式将能够执行多个 DataFrame 替代方案可以做的任何事情,但速度更快,并且它会使保存到 HDFStore 变得简单。
  • 第二个问题的链接(有些相关的帖子):stackoverflow.com/q/28662373/4586746
  • @unutbu 感谢您的回复。我肯定有点同意。而且您不认为按照您的建议将其展平会导致已经很大的 DataFrame(150 万行和大约 9GB 的内存使用)出现任何性能问题?

标签: python-2.7 pandas pytables hdfstore


【解决方案1】:

我的直觉反应是,您可能不应该将 DataFrame 拆分为 许多较小的 DataFrame。处理大量小型 DataFrame 需要 Python 循环,这通常是通往缓慢之路的一步。相反,我认为你 使用一个 DataFrame 可能会更好,它会使 dicts 列表变平,因此 每个内部字典在 DataFrame 中都有自己的行。的钥匙 内部字典将成为新列。我怀疑这种单一的平面 DataFrame 格式 将能够做多个 DataFrame 替代品可以做的任何事情,但 速度更快,并且可以让保存到 HDFStore 变得简单。

假设您有一个 DataFrame,其中包含 RANKS 列中的字典列表:

import numpy as np
import pandas as pd

df = pd.DataFrame({'ID' : [13423,294847,322844,429847], 'RANKS': [[{u'name': u'A', u'price': u'$1.00', u'rank': u'1'},
{u'name': u'B', u'price': u'$4.00', u'rank': u'2'},
{u'name': u'C', u'price': u'$3.99', u'rank': u'3'},
{u'name': u'D', u'price': u'$2.00', u'rank': u'4'},
{u'name': u'E', u'price': u'$2.50', u'rank': u'5'}],

[{u'name': u'AA', u'price': u'$1.99', u'rank': u'1'},
{u'name': u'BB', u'price': u'$6.99', u'rank': u'2'}],

[{u'name': u'Z', u'price': u'$0.99', u'rank': u'1'},
{u'name': u'Y', u'price': u'$10.00', u'rank': u'2'},
{u'name': u'X', u'price': u'$1.99', u'rank': u'3'}],[]], 'count' : [5,2,3,0]})

然后您可以像这样构建一个每行一个字典的平面 DataFrame:

result = []
for idx, row in df.iterrows():
    for dct in row['RANKS']:
        dct['ID'] = row['ID']
        dct['count'] = row['count']
        result.append(dct)
del df
result = pd.DataFrame(result)
result['rank'] = result['rank'].astype(np.int32)
result['price'] = result['price'].str.replace('$', '')
result['price'] = result['price'].astype('float')
print(result)

产生

       ID  count name  price  rank
0   13423      5    A   1.00     1
1   13423      5    B   4.00     2
2   13423      5    C   3.99     3
3   13423      5    D   2.00     4
4   13423      5    E   2.50     5
5  294847      2   AA   1.99     1
6  294847      2   BB   6.99     2
7  322844      3    Z   0.99     1
8  322844      3    Y  10.00     2
9  322844      3    X   1.99     3

请注意,直接从原始数据源构建result(从而完全避免df)将是一种更简洁、内存需求更少的解决方案。

【讨论】:

  • 非常感谢。我真的很感激这个建议。我有一个想法,我很快就会尝试:为了解决内存问题,我将创建一个小数据框来展开嵌套列(根据您的代码),然后将其在 ID 上与主数据框合并。这样,就没有重复的“df”,只是一个由 ID 和排名信息组成的附加数据框。
  • 更新(有点晚了):这是我最终使用的策略。
  • 晚了五年,但这个答案终于解决了我一直在研究的一个问题,非常感谢!
【解决方案2】:

在Pandas 版本0.25.0 中有df.explode 用于列表爆炸的方法和一些用于dict 爆炸的小代码。

如果您的数据框是:

import numpy as np
import pandas as pd

df = pd.DataFrame({'ID' : [13423,294847,322844,429847], 'RANKS': [[{u'name': u'A', u'price': u'$1.00', u'rank': u'1'},
{u'name': u'B', u'price': u'$4.00', u'rank': u'2'},
{u'name': u'C', u'price': u'$3.99', u'rank': u'3'},
{u'name': u'D', u'price': u'$2.00', u'rank': u'4'},
{u'name': u'E', u'price': u'$2.50', u'rank': u'5'}],

[{u'name': u'AA', u'price': u'$1.99', u'rank': u'1'},
{u'name': u'BB', u'price': u'$6.99', u'rank': u'2'}],

[{u'name': u'Z', u'price': u'$0.99', u'rank': u'1'},
{u'name': u'Y', u'price': u'$10.00', u'rank': u'2'},
{u'name': u'X', u'price': u'$1.99', u'rank': u'3'}],[]], 'count' : [5,2,3,0]})

然后要分解列表,您可以执行以下操作:

df = df.explode('RANKS')

给你

    ID  RANKS   count
0   13423   {'name': 'A', 'price': '$1.00', 'rank': '1'}    5
0   13423   {'name': 'B', 'price': '$4.00', 'rank': '2'}    5
0   13423   {'name': 'C', 'price': '$3.99', 'rank': '3'}    5
0   13423   {'name': 'D', 'price': '$2.00', 'rank': '4'}    5
0   13423   {'name': 'E', 'price': '$2.50', 'rank': '5'}    5
1   294847  {'name': 'AA', 'price': '$1.99', 'rank': '1'}   2
1   294847  {'name': 'BB', 'price': '$6.99', 'rank': '2'}   2
2   322844  {'name': 'Z', 'price': '$0.99', 'rank': '1'}    3
2   322844  {'name': 'Y', 'price': '$10.00', 'rank': '2'}   3
2   322844  {'name': 'X', 'price': '$1.99', 'rank': '3'}    3
3   429847  NaN 0

要分解这些字典并将它们展开为列,您可以执行以下操作:

df.reset_index(drop=True, inplace=True)

# Replace NaN by empty dict
def replace_nans_with_dict(series):
    for idx in series[series.isnull()].index:
        series.at[idx] = {}
    return series



# Explodes list and dicts
def df_explosion(df, col_name:str):

    if df[col_name].isna().any():
        df[col_name] = replace_nans_with_dict(df[col_name])

    df.reset_index(drop=True, inplace=True)

    df1 = pd.DataFrame(df.loc[:,col_name].values.tolist())

    df = pd.concat([df,df1], axis=1)

    df.drop([col_name], axis=1, inplace=True)

    return df

运行

df = df_explosion(df, 'RANKS')

你将拥有:

ID  count   name    price   rank
0   13423   5   A   $1.00   1
1   13423   5   B   $4.00   2
2   13423   5   C   $3.99   3
3   13423   5   D   $2.00   4
4   13423   5   E   $2.50   5
5   294847  2   AA  $1.99   1
6   294847  2   BB  $6.99   2
7   322844  3   Z   $0.99   1
8   322844  3   Y   $10.00  2
9   322844  3   X   $1.99   3
10  429847  0   NaN NaN NaN

【讨论】:

    【解决方案3】:

    我刚刚遇到了类似的情况,最后我是这样解决的:

    In [1]: import pandas as pd
    
    In [2]: df = pd.DataFrame({
       ...:     'ID' : [13423,294847,322844,429847],
       ...:     'RANKS': [[{u'name': u'A', u'price': u'$1.00', u'rank': u'1'},
       ...:                {u'name': u'B', u'price': u'$4.00', u'rank': u'2'},
       ...:                {u'name': u'C', u'price': u'$3.99', u'rank': u'3'},
       ...:                {u'name': u'D', u'price': u'$2.00', u'rank': u'4'},
       ...:                {u'name': u'E', u'price': u'$2.50', u'rank': u'5'}],
       ...: 
       ...:               [{u'name': u'AA', u'price': u'$1.99', u'rank': u'1'},
       ...:                {u'name': u'BB', u'price': u'$6.99', u'rank': u'2'}],
       ...: 
       ...:               [{u'name': u'Z', u'price': u'$0.99', u'rank': u'1'},
       ...:                {u'name': u'Y', u'price': u'$10.00', u'rank': u'2'},
       ...:                {u'name': u'X', u'price': u'$1.99', u'rank': u'3'}],[]]})
    
    In [3]: import itertools
    
    In [4]: temp_df = pd.DataFrame(
       ...:     list(itertools.chain(*[zip([key]*len(val), val)
       ...:                            for key, val in df.RANKS.iteritems()])),
       ...:     columns=['idx', 'explode'])                  
    
    In [5]: exploded = pd.merge(
       ...:     df.drop('RANKS', axis=1),
       ...:     temp_df.explode.apply(pd.Series).join(temp_df.idx),
       ...:     left_index=True,
       ...:     right_on='idx',
       ...:     how='left').drop('idx', axis=1)
    

    分解后的数据框如下所示:

    In [6]: exploded
    Out[6]: 
           ID name   price rank
    0   13423    A   $1.00    1
    1   13423    B   $4.00    2
    2   13423    C   $3.99    3
    3   13423    D   $2.00    4
    4   13423    E   $2.50    5
    5  294847   AA   $1.99    1
    6  294847   BB   $6.99    2
    7  322844    Z   $0.99    1
    8  322844    Y  $10.00    2
    9  322844    X   $1.99    3
    9  429847  NaN     NaN  NaN
    

    【讨论】:

      猜你喜欢
      • 2020-08-19
      • 1970-01-01
      • 1970-01-01
      • 2021-04-13
      • 2018-08-26
      • 1970-01-01
      • 2021-05-05
      • 2017-12-18
      • 2023-01-29
      相关资源
      最近更新 更多