如何处理每个单元格中都有字典列表的 pandas 列

How to deal with pandas column that has a list of dicts in every cell

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

一个例子:

df = pd.DataFrame({'ID' : [13423,294847,322844,429847], 'RANKS': [[{u'name': u'A', u'price': u'.00', u'rank': u'1'},
{u'name': u'B', u'price': u'.00', u'rank': u'2'},
{u'name': u'C', u'price': u'.99', u'rank': u'3'},
{u'name': u'D', u'price': u'.00', u'rank': u'4'},
{u'name': u'E', u'price': u'.50', u'rank': u'5'}],

[{u'name': u'AA', u'price': u'.99', u'rank': u'1'},
{u'name': u'BB', u'price': u'.99', u'rank': u'2'}],

[{u'name': u'Z', u'price': u'[=11=].99', u'rank': u'1'},
{u'name': u'Y', u'price': u'.00', u'rank': u'2'},
{u'name': u'X', u'price': u'.99', u'rank': u'3'}],[]], 'count' : [5,2,3,0]})

请注意 'count' 是 'RANKS.' 中的字典数 我的目标是创建一系列额外的 dataframes/tables(每个 'rank' ) 和 link 这些到 HDFStore 中的主要 table。类似于:

Rank_2
ID       Price   Name
13423    .00    B  
294847   .99    BB 
322844   .99   Y 
429847   NaN      NaN   


Rank_3
ID       Price   Name
13423    .99    C  
294847   NaN      NaN 
322844   .99    X 
429847   NaN      NaN   

这样我可以在需要时轻松查询 ID 和排名,但主要 table 不会因展开此分层数据而变得混乱。

但是,问题是我不知道如何从该列创建 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 的目标?

我的直觉反应是你可能不应该将你的 DataFrame 分解成 许多较小的 DataFrame。处理大量小型 DataFrame 需要 Python 循环,这通常是通往缓慢之路的一步。相反,我认为你 使用一个 DataFrame 可能会更好,它将字典列表展平 每个内部字典在 DataFrame 中都有自己的行。的钥匙 inner dict 将成为新的列。我怀疑这种单一的平面 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'.00', u'rank': u'1'},
{u'name': u'B', u'price': u'.00', u'rank': u'2'},
{u'name': u'C', u'price': u'.99', u'rank': u'3'},
{u'name': u'D', u'price': u'.00', u'rank': u'4'},
{u'name': u'E', u'price': u'.50', u'rank': u'5'}],

[{u'name': u'AA', u'price': u'.99', u'rank': u'1'},
{u'name': u'BB', u'price': u'.99', u'rank': u'2'}],

[{u'name': u'Z', u'price': u'[=10=].99', u'rank': u'1'},
{u'name': u'Y', u'price': u'.00', u'rank': u'2'},
{u'name': u'X', u'price': u'.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)将是一种更清洁、更少内存需求的解决方案

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

In [1]: import pandas as pd

In [2]: df = pd.DataFrame({
   ...:     'ID' : [13423,294847,322844,429847],
   ...:     'RANKS': [[{u'name': u'A', u'price': u'.00', u'rank': u'1'},
   ...:                {u'name': u'B', u'price': u'.00', u'rank': u'2'},
   ...:                {u'name': u'C', u'price': u'.99', u'rank': u'3'},
   ...:                {u'name': u'D', u'price': u'.00', u'rank': u'4'},
   ...:                {u'name': u'E', u'price': u'.50', u'rank': u'5'}],
   ...: 
   ...:               [{u'name': u'AA', u'price': u'.99', u'rank': u'1'},
   ...:                {u'name': u'BB', u'price': u'.99', u'rank': u'2'}],
   ...: 
   ...:               [{u'name': u'Z', u'price': u'[=10=].99', u'rank': u'1'},
   ...:                {u'name': u'Y', u'price': u'.00', u'rank': u'2'},
   ...:                {u'name': u'X', u'price': u'.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   .00    1
1   13423    B   .00    2
2   13423    C   .99    3
3   13423    D   .00    4
4   13423    E   .50    5
5  294847   AA   .99    1
6  294847   BB   .99    2
7  322844    Z   [=11=].99    1
8  322844    Y  .00    2
9  322844    X   .99    3
9  429847  NaN     NaN  NaN

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

如果您的数据框是:

import numpy as np
import pandas as pd

df = pd.DataFrame({'ID' : [13423,294847,322844,429847], 'RANKS': [[{u'name': u'A', u'price': u'.00', u'rank': u'1'},
{u'name': u'B', u'price': u'.00', u'rank': u'2'},
{u'name': u'C', u'price': u'.99', u'rank': u'3'},
{u'name': u'D', u'price': u'.00', u'rank': u'4'},
{u'name': u'E', u'price': u'.50', u'rank': u'5'}],

[{u'name': u'AA', u'price': u'.99', u'rank': u'1'},
{u'name': u'BB', u'price': u'.99', u'rank': u'2'}],

[{u'name': u'Z', u'price': u'[=10=].99', u'rank': u'1'},
{u'name': u'Y', u'price': u'.00', u'rank': u'2'},
{u'name': u'X', u'price': u'.99', u'rank': u'3'}],[]], 'count' : [5,2,3,0]})

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

df = df.explode('RANKS')

这给了你

    ID  RANKS   count
0   13423   {'name': 'A', 'price': '.00', 'rank': '1'}    5
0   13423   {'name': 'B', 'price': '.00', 'rank': '2'}    5
0   13423   {'name': 'C', 'price': '.99', 'rank': '3'}    5
0   13423   {'name': 'D', 'price': '.00', 'rank': '4'}    5
0   13423   {'name': 'E', 'price': '.50', 'rank': '5'}    5
1   294847  {'name': 'AA', 'price': '.99', 'rank': '1'}   2
1   294847  {'name': 'BB', 'price': '.99', 'rank': '2'}   2
2   322844  {'name': 'Z', 'price': '[=12=].99', 'rank': '1'}    3
2   322844  {'name': 'Y', 'price': '.00', 'rank': '2'}   3
2   322844  {'name': 'X', 'price': '.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   .00   1
1   13423   5   B   .00   2
2   13423   5   C   .99   3
3   13423   5   D   .00   4
4   13423   5   E   .50   5
5   294847  2   AA  .99   1
6   294847  2   BB  .99   2
7   322844  3   Z   [=15=].99   1
8   322844  3   Y   .00  2
9   322844  3   X   .99   3
10  429847  0   NaN NaN NaN