【问题标题】:Pythonic way to handle complex nested dict structures处理复杂嵌套字典结构的 Pythonic 方法
【发布时间】:2018-02-16 19:16:03
【问题描述】:

我的代码库依赖于管理当前位于嵌套非常深的字典中的数据。示例:

    'USA': {
        'Texas': {
            'Austin': {
                '2017-01-01': 169,
                '2017-02-01': 231
            },
            'Houston': {
                '2017-01-01': 265,
                '2017-02-01': 310
            }
        }

这适用于多个国家、州/地区、城市和日期。

我在尝试访问值时遇到了一个问题,因为我需要一个深度嵌套的 for 循环来遍历每个国家、州、城市和日期以应用某种操作。我正在寻找某种替代方案。

  1. 假设嵌套的 dict 结构是相同的,有没有替代这么多循环的方法?也许使用 map、reduce 或 lambda?

  2. 有没有更好的方法来存储所有这些数据而不使用嵌套字典?

【问题讨论】:

标签: python dictionary data-structures


【解决方案1】:

您可以使用 Pandas DataFrame 对象 (Pandas Dataframe Documentation),它可以以表格格式存储您的数据,类似于电子表格。在这种情况下,您的DataFrame 应该有一列来表示嵌套数据中的每个键(一列代表国家,另一列代表州,依此类推)。

Pandas DataFrames 还可以根据您对每列的记录(行)进行过滤、分组和其他有用的操作。假设您要过滤数据以仅返回德克萨斯州发生在“2018-02-01”之后的行(df 是您的DataFrame)。这可以通过以下方式实现:

df[df['State'] == 'Texas' & df['Date'] > '2018-02-01']

要构建这些DataFrame 对象,您可以从格式化为记录集合的数据开始:

data = [['USA', 'Texas', 'Austin', '2017-01-01', 169],
['USA', 'Texas', 'Austin', '2017-02-01', 231],
['USA', 'Texas', 'Houston', '2017-01-01', 265],
['USA', 'Texas', 'Houston', '2017-02-01', 310]]

然后像这样构建它们:

df = DataFrame(data, columns=['Country', 'State', 'City', 'Date', 'Value'])

如果DataFrame 对象不是一个选项,并且您不想使用嵌套循环,您还可以使用带有嵌套谓词和过滤器的列表推导式访问内部数据:

[
   d[country][state][city][date]
   for country in d.keys()
   for state in d[country].keys()
   for city in d[country][state].keys()
   for date in d[country][state][city].keys()
   if country == 'USA' and state == 'Texas' and city == 'Houston'
]

但是,我看不出这种方法与嵌套循环有太大区别,而且代码可读性会受到影响,恕我直言。

使用前面提到的记录集合方法 (data),而不是嵌套结构,您可以使用以下方法过滤行:

[r for r in data if r[2] == 'Houston']

为了提高可读性,您可以使用namedtuple 对象列表作为记录列表。您的数据将是:

from collections import namedtuple
record = namedtuple('Record', 'country state city date value')
data = [
   record('USA', 'Texas', 'Austin', '2017-01-01', 169),
   record('USA', 'Texas', 'Austin', '2017-02-01', 231),
   record('USA', 'Texas', 'Houston', '2017-01-01', 265),
   record('USA', 'Texas', 'Houston', '2017-02-01', 310)
]

并且您的过滤将得到改进,例如:

获取特定记录

[r for r in data if r.city == 'Houston']

返回

[
    Record(country='USA', state='Texas', city='Houston', date='2017-01-01', value=265),
    Record(country='USA', state='Texas', city='Houston', date='2017-02-01', value=310)
]

只获取那些特定记录的值

[r.value for r in data if r.city == 'Houston']

返回

[265, 310]

考虑到namedtuple 对象可以轻松存储它们,最后一种方法也可以处理自定义对象实例。

【讨论】:

  • 我忘了澄清一件事,有时 dict 中的值实际上可能不是数据。例如,我们有时会在那里存储一个已初始化的对象,因此数据帧无法正常工作。
  • @halfcup 考虑到这种新情况,我更新了我的答案。
【解决方案2】:

你可以创建一个类,实现重载方法,并使用递归:

d = {'USA': {
    'Texas': {
        'Austin': {
            '2017-01-01': 169,
            '2017-02-01': 231
        },
        'Houston': {
            '2017-01-01': 265,
            '2017-02-01': 310
        }
      }
   }
 }
class StateData:
   def __init__(self, structure):
      self.structure = structure
      self.levels = {'country':0, 'state':1, 'city':2, 'date':3}
   def get_level(self, d, target, current= 0):
      total_listing = [((a, b) if target == 3 else a) if current == target else self.get_level(b, target, current + 1) for a, b in d.items()] 
      return [i for b in total_listing for i in b] if all(isinstance(i, list) for i in total_listing) else total_listing
   def __getitem__(self, val):
       return self.get_level(self.structure, self.levels[val])

s = StateData(d)
print(s['city'])
print(s['date'])

输出:

['Austin', 'Houston']
[('2017-01-01', 169), ('2017-02-01', 231), ('2017-01-01', 265), ('2017-02-01', 310)]

最好将您的数据存储为列表列表,这样您就可以根据每个单独操作的需要进行分组。例如:

state_data = [['USA', 'Texas', 'Austin', '2017-01-01', 169],
              ['USA', 'Texas', 'Austin', '2017-02-01', 231],
              ['USA', 'Houston', '2017-01-01', 265],
              ['USA', 'Houston', '2017-02-01', 310]]

【讨论】:

    【解决方案3】:

    还有this

    它允许您将 dict 扁平化为 pandas 数据框,如下所示:

    from pandas.io.json import json_normalize
    
    d = json.load(f)
    
    # Parent node of dict d is 'programs'
    n = json_normalize(d['programs'])
    

    【讨论】:

    • 虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接的答案可能会失效
    猜你喜欢
    • 2020-10-19
    • 1970-01-01
    • 2021-12-19
    • 1970-01-01
    • 1970-01-01
    • 2019-08-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多