如何将含嵌套数组的啤酒配方JSON转为扁平化Pandas DataFrame
啤酒配方JSON嵌套数组扁平化及KeyError问题解决
问题背景
拥有包含嵌套数组(如fermentables、hops等字段)的啤酒配方JSON数据,使用pandas.json_normalize无法完全扁平化;修改代码后出现KeyError: 0错误,需要适配不同配方嵌套数组长度不固定的场景,实现完整扁平化的DataFrame转换。
错误原因分析
- 索引不匹配:代码中执行
df = df.T转置DataFrame后,原JSON的键成为了新的索引,而非数字序列,因此使用df.at[0, 'hops']查找索引为0的行时,会因索引不存在触发KeyError。 - 错误的嵌套处理逻辑:直接将嵌套数组转为DataFrame并存入原表列中,并未实现真正的扁平化,反而导致表结构混乱,后续操作容易出错。
解决方案
方案1:针对嵌套数组分别展开(推荐,适配任意长度嵌套)
该方案将每个嵌套数组单独展开为带配方标识的DataFrame,适合需要单独分析发酵原料、酒花等子数据的场景,也可根据配方ID合并数据。
import json import pandas as pd # 加载JSON数据 filename = 'recipes_full copy.json' with open(filename, 'r') as f: try: json_data = json.load(f) print("JSON文件有效") except ValueError as e: print("JSON文件无效:", e) # 统一数据格式:如果原始JSON是字典(键为配方ID),转为带配方ID的列表 if isinstance(json_data, dict): recipes_list = [{"recipe_id": recipe_key, **recipe_info} for recipe_key, recipe_info in json_data.items()] else: recipes_list = json_data # 展开fermentables(发酵原料)字段,保留配方顶层信息 df_fermentables = pd.json_normalize( recipes_list, record_path='fermentables', # 指定要展开的嵌套数组字段 meta=['recipe_id', 'name', 'style'] # 保留配方的顶层字段,按需添加 ) # 展开hops(酒花)字段,保留配方顶层信息 df_hops = pd.json_normalize( recipes_list, record_path='hops', meta=['recipe_id', 'name', 'style'] ) # 可选:根据recipe_id合并两个表(注意:若一个配方有多个发酵原料和酒花,会产生笛卡尔积,按需选择) # df_combined = pd.merge(df_fermentables, df_hops, on='recipe_id', suffixes=('_ferm', '_hop'))
方案2:将嵌套数组扁平化到同一行(生成多列)
如果需要把每个配方的所有嵌套信息放在同一行,会生成类似fermentables_0_name、fermentables_1_name的列,适合嵌套数组长度较短的场景。
import json import pandas as pd # 加载并格式化数据(同方案1) filename = 'recipes_full copy.json' with open(filename, 'r') as f: try: json_data = json.load(f) print("JSON文件有效") except ValueError as e: print("JSON文件无效:", e) if isinstance(json_data, dict): recipes_list = [{"recipe_id": k, **v} for k, v in json_data.items()] else: recipes_list = json_data # 定义扁平化嵌套字段的函数 def flatten_nested_column(df, nested_col_name): # 将嵌套数组展开为多列,添加前缀区分 nested_df = df[nested_col_name].apply(pd.Series).add_prefix(f"{nested_col_name}_") # 合并回原表并删除原嵌套列 return pd.concat([df.drop(nested_col_name, axis=1), nested_df], axis=1) # 初始化顶层配方DataFrame df_recipes = pd.DataFrame(recipes_list) # 依次扁平化fermentables和hops df_recipes = flatten_nested_column(df_recipes, 'fermentables') df_recipes = flatten_nested_column(df_recipes, 'hops') # 查看结果 print(df_recipes.head())
关键说明
- 若原始JSON是字典结构(键为配方唯一标识),必须先转为带
recipe_id的列表,否则json_normalize无法正确识别嵌套关系。 - 方案1的展开方式更灵活,能适配任意长度的嵌套数组,且不会因嵌套项数量不同导致数据缺失;方案2适合需要紧凑展示单配方全量信息的场景,但嵌套项过多时会生成大量列。
内容的提问来源于stack exchange,提问作者Luis Valencia
相关产品推荐
相关产品推荐

