python - 展平指定键上的字典列表

标签 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 的数组,第二条和第三条记录不是数组。 所需的输出是(作为数据帧):

    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 的空字典是理想的行为。

首先,我将 id 和数据提取到单独的列表中。我借此机会将字典放入字典列表(长度为 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, 1, 2, 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,如上所示。但是……我对 list 的所有困惑都是必要的吗?我无法执行简单的 json_normalize,因为我不想提取所有键,并且它会因数组和非数组的组合而失败。

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

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

最佳答案

你可以这样做:

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}

关于python - 展平指定键上的字典列表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53857263/

相关文章:

python - mpatches.FancyArrowPatch 太短

python - 正则表达式。匹配包含特殊字符或 'http://' 的单词

c - 使用空格分隔符将文件读入 C 数组

javascript - 查找对象数组的平均值

ios - 使用表格 View

javascript - VS 代码 - 引用错误 : $ is not defined WHY?

java - 如何以编程方式根据 Java 中的 Json 模式验证 Json 字符串?

python - 用 cython 添加数组比 numpy 慢?

Javascript 循环 - 第二个变量不显示

python - 比较前一个索引处的列表