【问题标题】:python pandas unnest data column containing a list of dictionaries包含字典列表的python pandas unnest数据列
【发布时间】:2021-11-19 07:59:47
【问题描述】:

我们有以下数据框:

import pandas as pd
our_df = pd.DataFrame(data = {'rank': {0: 1, 1: 2}, 'title_name': {0: "And It's Still Alright", 1: 'Black Madonna'}, 'title_id': {0: '120034150', 1: '106938609'}, 'artist_id': {0: '222521', 1: '200160'}, 'artist_name': {0: 'Nathaniel Rateliff', 1: 'Cage The Elephant'}, 'label': {0: 'CNCO', 1: 'RCA'}, 'metrics': {0: [{'name': 'Rank', 'value': 1}, {'name': 'Song', 'value': "And It's Still Alright"}, {'name': 'Artist', 'value': 'Nathaniel Rateliff'}, {'name': 'TP Spins', 'value': 933}, {'name': '+/- Chg. Spins', 'value': -32}, {'name': 'LP Spins', 'value': 965}, {'name': 'Stations', 'value': '44/46'}, {'name': 'Adds', 'value': 0}, {'name': 'TP Audience', 'value': 1260000}, {'name': '+/- Chg. Audience', 'value': -40600}, {'name': 'LP Audience', 'value': 1300600}, {'name': 'TP Stream', 'value': 413101}], 1: [{'name': 'Rank', 'value': 2}, {'name': 'Song', 'value': 'Black Madonna'}, {'name': 'Artist', 'value': 'Cage The Elephant'}, {'name': 'TP Spins', 'value': 814}, {'name': '+/- Chg. Spins', 'value': 38}, {'name': 'LP Spins', 'value': 776}, {'name': 'Stations', 'value': '38/46'}, {'name': 'Adds', 'value': 0}, {'name': 'TP Audience', 'value': 1283400}, {'name': '+/- Chg. Audience', 'value': -21600}, {'name': 'LP Audience', 'value': 1305000}, {'name': 'TP Stream', 'value': 362366}]}})

我们希望将 metrics 列转换为数据框中的 12 个新列,使用指标的 name 字段作为列名,value 字段作为数据框中的字段。像这样的:

rank               title_name  title_id  artist_id          artist_name    label  Rank                      Song ...   
1    'And It's Still Alright' 120034150     222521 'Nathaniel Rateliff'   'CNCO'     1  "And It's Still Alright"

这是第 1 行 metrics 列中的值:

our_df['metrics'][0]
[{'name': 'Rank', 'value': 1},
 {'name': 'Song', 'value': "And It's Still Alright"},
 {'name': 'Artist', 'value': 'Nathaniel Rateliff'},
 {'name': 'TP Spins', 'value': 933},
 {'name': '+/- Chg. Spins', 'value': -32},
 {'name': 'LP Spins', 'value': 965},
 {'name': 'Stations', 'value': '44/46'},
 {'name': 'Adds', 'value': 0},
 {'name': 'TP Audience', 'value': 1260000},
 {'name': '+/- Chg. Audience', 'value': -40600},
 {'name': 'LP Audience', 'value': 1300600},
 {'name': 'TP Stream', 'value': 413101}]

尽管列名中的+/- 以及Chg. 中的. 可能存在问题,如果所有列名都是snake_case,如果+/- 被替换为plus_minus,则此数据框将是最好的, 如果 Chg. 中的 . 被简单地删除了。

编辑:我们可以假设数据框中每一行的指标名称都是相同的。但是,可能还有其他具有不同度量名称的数据帧,因此最好不要对名称“Rank”、“Song”、“Artist”等进行硬编码。这是转换为 pandas 数据框之前的原始列表:

raw_data = [{'rank': 1,
  'title_name': 'BUTTER',
  'title_id': '',
  'artist_id': '',
  'artist_name': 'BTS',
  'label': '',
  'peak_position': 1,
  'last_week_rank': 7,
  'last_2week_rank': 8,
  'metrics': [{'name': 'Rank', 'value': 1},
   {'name': 'Song', 'value': 'BUTTER'},
   {'name': 'Artist', 'value': 'BTS'},
   {'name': 'Label Description', 'value': None},
   {'name': 'Label', 'value': '    '},
   {'name': 'Last Week Rank', 'value': 7},
   {'name': 'Last 2 Week Rank', 'value': 8},
   {'name': 'Weeks On Chart', 'value': 15}]},
 {'rank': 2,
  'title_name': 'STAY',
  'title_id': '',
  'artist_id': '',
  'artist_name': 'THE KID LAROI & JUS',
  'label': '',
  'peak_position': 1,
  'last_week_rank': 1,
  'last_2week_rank': 1,
  'metrics': [{'name': 'Rank', 'value': 2},
   {'name': 'Song', 'value': 'STAY'},
   {'name': 'Artist', 'value': 'THE KID LAROI & JUS'},
   {'name': 'Label Description', 'value': None},
   {'name': 'Label', 'value': '    '},
   {'name': 'Last Week Rank', 'value': 1},
   {'name': 'Last 2 Week Rank', 'value': 1},
   {'name': 'Weeks On Chart', 'value': 8}]}]

【问题讨论】:

  • 为什么要在数据框中捕获数据?在创建数据框之前,您应该能够在 vanilla python 中进行数据处理。您还可以指出您尝试过的内容
  • 我将使用原始列表编辑问题,但是在 pandas 中数据 munge 通常不是比 vanilla python 更容易吗?
  • 在 vanilla python 中处理字典/列表比 Pandas 好得多
  • @sammywemmy 很高兴知道这一点。我刚刚用原始字典列表编辑了这个问题。

标签: python pandas dataframe


【解决方案1】:

最有可能的是,最快的方法是将raw_data 处理为字典,然后才用它构造一个DataFrame。

records = []
for rec in raw_data:
    for metric in rec['metrics']:
        # process name: snake_case > drop '.' > '+/-' to 'plus_minus'
        name = metric['name'].lower().replace(' ','_').replace('.','').replace('+/-','plus_minus')
        rec[name] = metric['value']
    rec.pop('metrics')  # drop metric records
    records.append(rec)

df = pd.DataFrame(records)

输出

结果df

rank title_name title_id artist_id artist_name label peak_position last_week_rank last_2week_rank song artist label_description last_2_week_rank weeks_on_chart
0 1 BUTTER BTS 1 7 8 BUTTER BTS 8 15
1 2 STAY THE KID LAROI & JUS 1 1 1 STAY THE KID LAROI & JUS 1 8

设置

raw_data = [{'rank': 1,
  'title_name': 'BUTTER',
  'title_id': '',
  'artist_id': '',
  'artist_name': 'BTS',
  'label': '',
  'peak_position': 1,
  'last_week_rank': 7,
  'last_2week_rank': 8,
  'metrics': [{'name': 'Rank', 'value': 1},
   {'name': 'Song', 'value': 'BUTTER'},
   {'name': 'Artist', 'value': 'BTS'},
   {'name': 'Label Description', 'value': None},
   {'name': 'Label', 'value': '    '},
   {'name': 'Last Week Rank', 'value': 7},
   {'name': 'Last 2 Week Rank', 'value': 8},
   {'name': 'Weeks On Chart', 'value': 15}]},
 {'rank': 2,
  'title_name': 'STAY',
  'title_id': '',
  'artist_id': '',
  'artist_name': 'THE KID LAROI & JUS',
  'label': '',
  'peak_position': 1,
  'last_week_rank': 1,
  'last_2week_rank': 1,
  'metrics': [{'name': 'Rank', 'value': 2},
   {'name': 'Song', 'value': 'STAY'},
   {'name': 'Artist', 'value': 'THE KID LAROI & JUS'},
   {'name': 'Label Description', 'value': None},
   {'name': 'Label', 'value': '    '},
   {'name': 'Last Week Rank', 'value': 1},
   {'name': 'Last 2 Week Rank', 'value': 1},
   {'name': 'Weeks On Chart', 'value': 8}]}]

使用示例的数据为raw_data,即

our_df = pd.DataFrame(data = {'rank': {0: 1, 1: 2}, 'title_name': {0: "And It's Still Alright", 1: 'Black Madonna'}, 'title_id': {0: '120034150', 1: '106938609'}, 'artist_id': {0: '222521', 1: '200160'}, 'artist_name': {0: 'Nathaniel Rateliff', 1: 'Cage The Elephant'}, 'label': {0: 'CNCO', 1: 'RCA'}, 'metrics': {0: [{'name': 'Rank', 'value': 1}, {'name': 'Song', 'value': "And It's Still Alright"}, {'name': 'Artist', 'value': 'Nathaniel Rateliff'}, {'name': 'TP Spins', 'value': 933}, {'name': '+/- Chg. Spins', 'value': -32}, {'name': 'LP Spins', 'value': 965}, {'name': 'Stations', 'value': '44/46'}, {'name': 'Adds', 'value': 0}, {'name': 'TP Audience', 'value': 1260000}, {'name': '+/- Chg. Audience', 'value': -40600}, {'name': 'LP Audience', 'value': 1300600}, {'name': 'TP Stream', 'value': 413101}], 1: [{'name': 'Rank', 'value': 2}, {'name': 'Song', 'value': 'Black Madonna'}, {'name': 'Artist', 'value': 'Cage The Elephant'}, {'name': 'TP Spins', 'value': 814}, {'name': '+/- Chg. Spins', 'value': 38}, {'name': 'LP Spins', 'value': 776}, {'name': 'Stations', 'value': '38/46'}, {'name': 'Adds', 'value': 0}, {'name': 'TP Audience', 'value': 1283400}, {'name': '+/- Chg. Audience', 'value': -21600}, {'name': 'LP Audience', 'value': 1305000}, {'name': 'TP Stream', 'value': 362366}]}})

raw_data = our_df.to_dict(orient='records')

输出

从上面的解决方案得到df

rank title_name title_id artist_id artist_name label song artist tp_spins plus_minus_chg_spins lp_spins stations adds tp_audience plus_minus_chg_audience lp_audience tp_stream
0 1 And It's Still Alright 120034150 222521 Nathaniel Rateliff CNCO And It's Still Alright Nathaniel Rateliff 933 -32 965 44/46 0 1260000 -40600 1300600 413101
1 2 Black Madonna 106938609 200160 Cage The Elephant RCA Black Madonna Cage The Elephant 814 38 776 38/46 0 1283400 -21600 1305000 362366

【讨论】:

    【解决方案2】:

    让我们开始分解您的问题。在定义our_df 之后,我们可以根据metrics 列生成一个新的数据框:

    pd.concat([pd.DataFrame({x['name']:x['value'] for x in y},index=[0]) for y in our_df['metrics']]
    

    哪些输出:

       Rank                    Song  ... LP Audience  TP Stream
    0     1  And It's Still Alright  ...     1300600     413101
    0     2           Black Madonna  ...     1305000     362366
    

    接下来只是将它们与pd.concat() 连接在一起或合并的问题。我假设公共键是Rank 列,因此我将使用合并:

    our_df.drop(columns=['metrics']).merge(pd.concat([pd.DataFrame({x['name']:x['value'] for x in y},index=[0]) for y in our_df['metrics']]),left_on='rank',right_on='Rank')
    

    输出完整的数据帧

        rank              title_name  ... LP Audience TP Stream
    0     1  And It's Still Alright  ...     1300600    413101
    1     2           Black Madonna  ...     1305000    362366
    

    【讨论】:

      【解决方案3】:

      可能对丢失 names 很有效的替代方案

      metric_df = our_df.apply(
          lambda r:
              pd.Series(
                  index=list(map(lambda d: d['name'], r['metrics']))+['rank'],
                  data=list(map(lambda d: d['value'], r['metrics']))+[r['rank']],
              ),
          axis=1,
      )
      
      our_df.merge(metric_df, on='rank')
      

      【讨论】:

        【解决方案4】:
        box = pd.concat({index  : pd.DataFrame(ent) 
                         for index, ent in 
                         zip( our_df.index, our_df.metrics)})
        
        ( our_df
          .drop(columns = 'metrics')
          .join(box.droplevel(-1))
          .pivot(['rank', 'title_name', 'title_id', 'artist_id', 'artist_name', 'label'], 
                  'name', 
                  'value')
          .reset_index()
        )
        
        name  rank              title_name   title_id artist_id         artist_name label +/- Chg. Audience +/- Chg. Spins Adds              Artist LP Audience LP Spins Rank                    Song Stations TP Audience TP Spins TP Stream
        0        1  And It's Still Alright  120034150    222521  Nathaniel Rateliff  CNCO            -40600            -32    0  Nathaniel Rateliff     1300600      965    1  And It's Still Alright    44/46     1260000      933    413101
        1        2           Black Madonna  106938609    200160   Cage The Elephant   RCA            -21600             38    0   Cage The Elephant     1305000      776    2           Black Madonna    38/46     1283400      814    362366
        

        处理 raw_data:

        from itertools import chain, product
        
        metrics = [ent['metrics']  for ent in raw_data]
        non_metrics = [{key : value 
                        for key, value 
                        in ent.items() 
                        if key != 'metrics'} 
                        for ent in raw_data]
        
        combo = zip(metrics, non_metrics)
        combo = (product(metrics, [non_metrics]) 
                 for metrics, non_metrics in combo)
        combo = chain.from_iterable(combo)
        combo = [{**left, **right} for left, right in combo]
        
        pd.DataFrame(combo)
        
                         name                value  rank title_name title_id artist_id          artist_name label  peak_position  last_week_rank  last_2week_rank
        0                Rank                    1     1     BUTTER                                     BTS                    1               7                8
        1                Song               BUTTER     1     BUTTER                                     BTS                    1               7                8
        2              Artist                  BTS     1     BUTTER                                     BTS                    1               7                8
        3   Label Description                 None     1     BUTTER                                     BTS                    1               7                8
        4               Label                          1     BUTTER                                     BTS                    1               7                8
        5      Last Week Rank                    7     1     BUTTER                                     BTS                    1               7                8
        6    Last 2 Week Rank                    8     1     BUTTER                                     BTS                    1               7                8
        7      Weeks On Chart                   15     1     BUTTER                                     BTS                    1               7                8
        8                Rank                    2     2       STAY                     THE KID LAROI & JUS                    1               1                1
        9                Song                 STAY     2       STAY                     THE KID LAROI & JUS                    1               1                1
        10             Artist  THE KID LAROI & JUS     2       STAY                     THE KID LAROI & JUS                    1               1                1
        11  Label Description                 None     2       STAY                     THE KID LAROI & JUS                    1               1                1
        12              Label                          2       STAY                     THE KID LAROI & JUS                    1               1                1
        13     Last Week Rank                    1     2       STAY                     THE KID LAROI & JUS                    1               1                1
        14   Last 2 Week Rank                    1     2       STAY                     THE KID LAROI & JUS                    1               1                1
        15     Weeks On Chart                    8     2       STAY                     THE KID LAROI & JUS                    1               1                1
        

        然后你可以重塑/转换成你想要的任何东西。

        【讨论】:

          猜你喜欢
          • 2022-12-03
          • 2021-10-04
          • 2022-10-13
          • 1970-01-01
          • 2016-11-23
          • 2021-02-15
          • 2021-07-10
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多