【发布时间】:2022-11-27 04:39:21
【问题描述】:
输入是许多结构不同的 JSON 文件,所需的输出是单个数据帧。
输入说明:
每个JSON 文件可能有 1 个或多个攻击者和 1 个受害者。 attackers 键指向字典列表。每个字典都是 1 个攻击者,具有 character_id、corporation_id、alliance_id 等键。victim 键指向具有相似键的字典。这里需要注意的重要一点是,相同 JSON 之间的键可能不同。例如,一个 JSON 文件可能有 attackers 键,如下所示:
{
"attackers": [
{
"alliance_id": 99005678,
"character_id": 94336577,
"corporation_id": 98224639,
"damage_done": 3141,
"faction_id": 500003,
"final_blow": true,
"security_status": -9.4,
"ship_type_id": 73796,
"weapon_type_id": 3178
},
{
"damage_done": 1614,
"faction_id": 500003,
"final_blow": false,
"security_status": 0,
"ship_type_id": 32963
}
],
...
这里的 JSON 文件有 2 个攻击者。但只有第一个攻击者拥有上述密钥。同样,victim 可能如下所示:
...
"victim": {
"character_id": 2119076173,
"corporation_id": 98725195,
"damage_taken": 4755,
"faction_id": 500002,
"items": [...
...
输出说明:
作为输出,我想从存储在同一目录中的许多(大约 400,000 个)此类 JSON 文件创建一个数据框。结果数据帧的每一行都应该有 1 名攻击者和 1 名受害者。具有多个攻击者的 JSON 应拆分为相同数量的行,其中攻击者的属性不同,但受害者属性相同。例如,如果有 3 名攻击者,则为 3 行;如果某个攻击者没有键值对,则为 NaN 值。因此,上述示例数据框中第二个攻击者的 character_id 应该是 NaN。
当前方法:
为此,我首先创建一个空列表。然后遍历所有文件,打开它们,将它们加载为 JSON 对象,转换为数据帧,然后将数据帧附加到列表中。请注意pd.DataFrame([json.load(fi)]) 与pd.json_normalize(json.load(fi)) 具有相同的输出。
mainframe = []
for file in tqdm(os.listdir("D:/Master/killmails_jul"), ncols=100, ascii=' >'):
with open("%s/%s" % ("D:/Master/killmails_jul", file),'r') as fi:
mainframe.append(pd.DataFrame([json.load(fi)]))
在这个循环之后,我留下了一个数据帧列表,我使用 pd.concat() 将它们连接起来。
mainframe = pd.concat(mainframe)
到目前为止,无论attackers 的数量如何,数据框每个 JSON 只有 1 行。为了解决这个问题,我在下一步中使用pd.explode()。
mainframe = mainframe.explode('attackers')
mainframe.reset_index(drop=True, inplace=True)
现在每个攻击者都有单独的行,但是 attackers 和 victim 键仍然隐藏在各自的列中。为了解决这个问题,我通过 pd.apply(pd.Series) 水平“分解”了两列,并应用前缀以便于识别,如下所示:
intframe = mainframe["attackers"].apply(pd.Series).add_prefix("attackers_").join(mainframe["victim"].apply(pd.Series).add_prefix("victim_"))
在下一步中,我将这个中间框架与大型机结合起来以保留 killmail_id 和 killmail_hash 列。然后删除 attackers 和 victim 列,因为我现在已经展开了它们。
mainframe = intframe.join(mainframe)
mainframe.fillna(0, inplace=True)
mainframe.drop(['attackers','victim'], axis=1, inplace=True)
这为我提供了包含以下 24 列的所需输出:
['attackers_character_id', 'attackers_corporation_id', 'attackers_damage_done', 'attackers_final_blow', 'attackers_security_status', 'attackers_ship_type_id', 'attackers_weapon_type_id', 'attackers_faction_id', 'attackers_alliance_id', 'victim_character_id', 'victim_corporation_agen_id', victim_items', 'victim_position', 'victim_ship_type_id', 'victim_alliance_id', 'victim_faction_id', 'killmail_id', 'killmail_time', 'solar_system_id', 'killmail_hash', 'http_last_modified', 'war_id', 'moon_id']
问题:
有没有比我现在做的更好的方法?我尝试使用发电机,但无法让它们工作。我得到一个AttributeError: 'str' object has no attribute 'read'
all_files_paths = glob(os.path.join('D:\\Master\\kmrest', '*.json'))
def gen_df(files):
for file in files:
with open(file, 'r'):
data = json.load(file)
data = pd.DataFrame([data])
yield data
mainframe = pd.concat(gen_df(all_files_paths), ignore_index=True)
将 pd.concat() 函数与生成器一起使用会导致二次复制吗?
另外,我担心打开和关闭许多文件会减慢计算速度。也许最好先从所有 JSON 创建一个 JSONL 文件,然后为每一行创建一个数据框。
如果你想获得这些文件,我正在尝试与你合作,可以单击here。让我知道是否需要更多信息。
【问题讨论】:
-
items每个受害者的预期待遇是什么?
标签: python json pandas dataframe