【问题标题】:Explode pandas column of dictionary with list of tuples as value以元组列表为值展开字典的 pandas 列
【发布时间】:2022-02-03 00:54:07
【问题描述】:

我有以下数据框,其中 col2 是一个字典,其中包含一个元组列表作为值。键在整个数据框中始终“添加”和“删除”。

输入df

col1 col2
value1 {'added': [(59, 'dep1_v2'), (60, 'dep2_v2')], 'deleted': [(59, 'dep1_v1'), (60, 'dep2_v1')]}
value 2 {'added': [(61, 'dep3_v2')], 'deleted': [(61, 'dep3_v1')]}

这是一个可复制粘贴的示例数据框:

jsons = ["{'added': [(59, 'dep1_v2'), (60, 'dep2_v2')], 'deleted': [(59, 'dep1_v1'), (60, 'dep2_v1')]}",
         "{'added': [(61, 'dep3_v2')], 'deleted': [(61, 'dep3_v1')]}"]

df = pd.DataFrame({"col1": ["value1", "value2"], "col2": jsons})

编辑

col2 直接来自diff_parsed field of pydriller 输出

我想“分解” col2 以获得以下结果:

期望的输出

col1 number added deleted
value1 59 dep1_v2 dep1_v1
value1 60 dep2_v2 dep2_v1
value2 61 dep3_v2 dep3_v1

到目前为止,我尝试了以下方法:

df = df.join(pd.json_normalize(df.col2))
df.drop(columns=['col2'], inplace=True)

上面的代码被简化了。我首先操作该列以转换为正确的 json。它试图首先在“添加”和“删除”上爆炸,然后尝试使用格式来获​​得我想要的......但是元组列表没有保留,我得到以下内容:

col1 added deleted
value1 59, dep1_v2, 60, dep2_v2 59, dep1_v1, 60, dep2_v1
value2 61, dep3_v1 61, dep3_v2

谢谢

【问题讨论】:

    标签: python pandas dataframe


    【解决方案1】:

    这当然不优雅,但这是一个至少更容易理解和推理的潜在解决方案:

    def explode_records(df):
        new_records = []
        def map_dict_to_row(value, col2_dict):
            temp = {}
            for number, added in col2_dict["added"]:
                temp[number] = {"value": value, "number": number, "added": added}
            for number, deleted in col2_dict["deleted"]:
                if number in temp:
                    temp[number] = {**temp[number], "deleted": deleted}
                else:
                    temp[number] = {"value": value, "deleted": deleted}
            new_records.extend(list(temp.values()))
    
        df.apply(lambda row: map_dict_to_row(row.col1, row.col2), axis=1)  # assumes col2 is a dict
        return pd.DataFrame(new_records)
    

    用法:

    In [4]: explode_records(df)
    Out[4]:
         value  number    added  deleted
    0   value1      59  dep1_v2  dep1_v1
    1   value1      60  dep2_v2  dep2_v1
    2  value 2      61  dep3_v2  dep3_v1
    

    请注意,我从您的原始数据中获得了 value 2。我假设这只是一个错字,而不是您也需要value x -> valuex 功能。

    我无法让其他解决方案发挥作用,因此无法将其性能与我的进行比较。

    【讨论】:

    • 解决方案不需要很优雅,但这需要太长时间才能在我的数据上运行。当使用较小的样本进行测试时,我会得到 KeyError: ' added' 或 "ValueError: malformed node or string",具体取决于我事先解析 json 的方式。
    • 如果数据已经是字典,那么ast.literal_eval会失败。从您的原始帖子中不清楚col2 是什么类型,而且您似乎正在使用json 字符串。如果您确实从 diff_parsed 中获取字典,则删除 ast.literal_eval() 函数调用,然后执行 f(r.col1, r.col2)。查看我的编辑。
    • 第二个for 循环不会在这种情况下运行。您应该在该记录的deleted 列中获得NaN。
    • 啊,我明白了。该解决方案假定总会有一个added。检查编辑。如果您遇到其他问题,希望从那里开始就足够了。
    • 非常好!我只在我的数据集的一个随机样本上测试了可接受的解决方案,所以我在运行完整的东西时发现了这一点。现在运行平稳,性能良好。再次感谢。
    【解决方案2】:

    获取添加和删除的数据(col2中的值,已转换为dicts):

    added, deleted = [df.col2.str[head].explode()
                        .apply(pd.Series).set_axis(['number', head], axis = 1) 
                      for head in ('added', 'deleted')]
    

    去掉重复的数字列:

     added = added['added']
    

    修剪 df:

    df = df['col1']
    

    连接:

    pd.concat([df, added, deleted], axis = 1)
    
          col1    added  number  deleted
    0   value1  dep1_v2      59  dep1_v1
    0   value1  dep2_v2      60  dep2_v1
    1  value 2  dep3_v2      61  dep3_v1
    

    @ddejohn 的解决方案应该更高效,因为您正在处理原生 python 结构,然后再将它们转储到 pandas 中

    【讨论】:

    • 这是我正在寻找的解决方案。但是,我收到“ValueError:长度不匹配:预期轴有 1 个元素,新值有 2 个元素”。
    • 请分享更多应该反映此错误的行。我假设您已经在共享的数据上对此进行了测试,并且失败来自实际的更大数据集
    • 错误来自for head in ('added', 'deleted')。我的数据直接来自 [pydriller.readthedocs.io/en/latest/… 的diff_parsed 输出。我只是按原样将字典输出到 csv 行。样本两行两列(简化哈希):df = pd.DataFrame({'hash': ['hash1', 'hash2'], 'diff_parsed': [{'added': [(78, ' "jest": "^27.2.5",')], 'deleted': [(78, ' "jest": "^27.2.4",')]}, {'added': [(129, ' "typescript": "3.5.1",')], 'deleted': [(129, ' "typescript": "3.4.5",')]}]}) Col diff_parsed 是 object dtype。抱歉,这很丑。
    • 不用担心。 @ddejohn 解决方案很好
    【解决方案3】:

    这里有一个解决方案。它有点长,但它有效:

    tmp = pd.concat([df, pd.json_normalize(df['col2'])], axis=1).drop('col2', axis=1).explode(['added', 'deleted'])
    new_df = pd.concat([tmp.drop(['added', 'deleted'], axis=1).reset_index(drop=True), pd.DataFrame(tmp['added'].tolist()).merge(pd.DataFrame(tmp['deleted'].tolist()), on=0).set_axis(['number', 'added', 'deleted'], axis=1)], axis=1)
    

    输出:

    >>> new_df
         col1  number    added  deleted
    0  value1      59  dep1_v2  dep1_v1
    1  value1      60  dep2_v2  dep2_v1
    2  value2      61  dep3_v2  dep3_v1
    

    【讨论】:

    • 我用这个解决方案在"added" 上得到了一个KeyError。您是如何重新创建 OP 的数据框的?
    • @ddejohn I 1) 将 SO 显示的表格复制并粘贴到文本文件中,2) 将制表符替换为空格,3) 将单元格中的所有空格替换为 X,4) 复制结果并使用了pd.read_csv, 5) 将X 替换为` `(空格)。你知道更好的方法吗?这有点乏味...
    • 只是稍微简单一点:复制粘贴并用| 替换标签,然后再次复制,然后pd.read_clipboard(sep="|")。
    • 哇,这是一个更简单的方法!我喜欢它!谢谢你:)
    • 哈哈,是的,学习pd.read_clipboard() 为我节省了很多关于 SO 的 Pandas 问题的时间:D
    猜你喜欢
    • 1970-01-01
    • 2011-09-05
    • 1970-01-01
    • 2018-05-30
    • 2020-06-22
    • 2020-08-21
    • 1970-01-01
    • 2021-09-07
    • 2020-04-01
    相关资源
    最近更新 更多