Pythonic方式处理复杂的嵌套dict结构

时间:2018-02-16 19:16:03

标签: python dictionary data-structures

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

    '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. 有没有更好的方法来存储所有这些数据而不使用嵌套的dicts?

3 个答案:

答案 0 :(得分:2)

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

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'
]

但是,我看不出这种方法在嵌套循环上有太大差异,并且代码可读性存在损失,imho。

使用前面指出的记录集合方法(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对象可以轻松存储它们,最后一种方法也可以处理自定义对象实例。

答案 1 :(得分:0)

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

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]]

答案 2 :(得分:-2)

还有this

它可让您将字典拼写成这样的pandas数据框:

from pandas.io.json import json_normalize

d = json.load(f)

# Parent node of dict d is 'programs'
n = json_normalize(d['programs'])