展平指定键上的字典列表

时间:2018-12-19 18:31:49

标签: python arrays json dictionary

目标:要从其中包含JSON(数组)的列的SQL表中读取数据,请从JSON中提取某些键/值到新列中,然后写入新表中。原始数据格式的乐趣之一是某些数据记录是JSON数组,而某些不是数组(仅仅是JSON)。因此,我们可以以开头:

testcase = [(1, [{'a': 1, 'b': 2, 'c': 3}, {'a': 11, 'b': 12, 'c': 13}]), 
            (2, {'a': 30, 'b': 40}), 
            (3, {'a': 100, 'b': 200, 'd': 300})]
for x in testcase:
    print(x)
(1, [{'a': 1, 'b': 2, 'c': 3}, {'a': 11, 'b': 12, 'c': 13}])
(2, {'a': 30, 'b': 40})
(3, {'a': 100, 'b': 200, 'd': 300})

请注意,每个元组的第一个元素是记录ID。第一条记录是长度为2的数组,第二条和第三条记录不是数组。 所需的输出(作为DataFrame):

    a   b   data
1   1   2   '{"c": 3}'
1   11  12  '{"c": 13}'
2   30  40  '{}'
3   100     200     '{"d": 300}'

在这里您可以看到我已将字典中的键“ a”和“ b”提取到新列中,其余键/值保留在原处。理想的行为是id = 2的空dict。

首先,我将ID和数据提取到单独的列表中。我借此机会将dict放入一系列dict(长度为1)中,以使类型现在保持一致:

id = [x[0] for x in testcase]
data_col = [x[1] if type(x[1]) == list else [x[1]] for x in testcase]
for x in data_col:
    print(x)
[{'a': 1, 'b': 2, 'c': 3}, {'a': 11, 'b': 12, 'c': 13}]
[{'a': 30, 'b': 40}]
[{'a': 100, 'b': 200, 'd': 300}]

必须提取id和data_col作为单独的列表,这是一个笨拙的额外步骤,尽管至少我们拥有不复制数据的好属性:

id[0] is testcase[0][0]
True
data_col[0] is testcase[0][1]
True

而且,正如我所说,我不得不处理这样的问题:某些记录包含字典数组,而某些记录包含字典,因此这使它们都保持一致。

主要问题发生在这里,我在双列表理解中执行字典理解以遍历每个字典:

popped = [(id, {key: element.pop(key, None) for key in ['a', 'b']}) \
for id, row in zip(id, data_col) for element in row]
for x in popped:
    print(x)
(1, {'a': 1, 'b': 2})
(1, {'a': 11, 'b': 12})
(2, {'a': 30, 'b': 40})
(3, {'a': 100, 'b': 200})

我需要能够将每个新行与其原始ID相关联,并且上述实现了这一点,正确地再现了适当的ID值(1、2、1、3)。进行一些整理,然后我就可以将所有目标行对齐:

import pandas as pd
from psycopg2.extras import Json
id2 = [x[0] for x in popped]
cols = [x[1] for x in popped]
data = [Json(item) for sublist in data_col for item in sublist]
popped_df = pd.DataFrame(cols, index=id2)
popped_df['data'] = data

这为我提供了所需的DataFrame,如上所示。但是...我是否需要弄乱列表?我无法做一个简单的json_normalize,因为我不想提取所有键,并且它会落入数组和非数组的组合中。

它还需要尽可能的高效,因为它将要处理数百万行。因此,我实际上使用以下方法将DataFrame转换为列表: 列表(popped_df.itertuples()) 然后传递给psycopg2.extras的execute_values() 所以我可能还没有去构建DataFrame并只构建输出列表,但是在这篇文章中,我真的在问是否有一种更干净,更快捷的方法来将这些特定键从dict中提取到新的列和行中,从而对record是否为数组,并跟踪相关的记录ID。

我回避了端到端的熊猫方法,因为在读取DataFrame.to_sql()相对较慢时,使用pd.read_sql()读取数据。

2 个答案:

答案 0 :(得分:0)

您的数据很混乱,因为testcase的第二个元素可以是list 一个dict。在这种情况下,您可以通过for循环构造一个列表,然后将其馈送到pd.DataFrame构造函数中:

testcase = [(1, [{'a': 1, 'b': 2, 'c': 3}, {'a': 11, 'b': 12, 'c': 13}]), 
            (2, {'a': 30, 'b': 40}), 
            (3, {'a': 100, 'b': 200, 'd': 300})]

L = []
for idx, data in testcase:
    for d in ([data] if isinstance(data, dict) else data):
        # string conversion not strictly necessary below
        others = str({k: v for k, v in d.items() if k not in ('a', 'b')})
        L.append((idx, d['a'], d['b'], others))

df = pd.DataFrame(L, columns=['index', 'a', 'b', 'data']).set_index('index')

print(df)

         a    b        data
index                      
1        1    2    {'c': 3}
1       11   12   {'c': 13}
2       30   40          {}
3      100  200  {'d': 300}

答案 1 :(得分:0)

您可以执行以下操作:

import pandas as pd

testcase = [(1, [{'a': 1, 'b': 2, 'c': 3}, {'a': 11, 'b': 12, 'c': 13}]),
            (2, {'a': 30, 'b': 40}),
            (3, {'a': 100, 'b': 200, 'd': 300})]


def split_dict(d, keys=['a', 'b']):
    """Split the dictionary by keys"""
    preserved = {key: value for key, value in d.items() if key in keys}
    complement = {key: value for key, value in d.items() if key not in keys}
    return preserved, complement


def get_row(val):
    preserved, complement = split_dict(val)
    preserved['data'] = complement
    return preserved


rows = []
index = []
for i, values in testcase:
    if isinstance(values, list):
        for value in values:
            rows.append(get_row(value))
            index.append(i)
    else:
        rows.append(get_row(values))
        index.append(i)


df = pd.DataFrame.from_records(rows, index=index)
print(df)

输出

     a    b        data
1    1    2    {'c': 3}
1   11   12   {'c': 13}
2   30   40          {}
3  100  200  {'d': 300}