将“熊猫”列中的字典/列表拆分为单独的列


146

我将数据保存在postgreSQL数据库中。我正在使用Python2.7查询此数据并将其转换为Pandas DataFrame。但是,此数据框的最后一列中包含值的字典(或列表?)。DataFrame看起来像这样:

[1] df
Station ID     Pollutants
8809           {"a": "46", "b": "3", "c": "12"}
8810           {"a": "36", "b": "5", "c": "8"}
8811           {"b": "2", "c": "7"}
8812           {"c": "11"}
8813           {"a": "82", "c": "15"}

我需要将此列拆分为单独的列,以便DataFrame如下所示:

[2] df2
Station ID     a      b       c
8809           46     3       12
8810           36     5       8
8811           NaN    2       7
8812           NaN    NaN     11
8813           82     NaN     15

我遇到的主要问题是列表的长度不同。但是所有列表最多只能包含相同的3个值:a,b和c。而且它们始终以相同的顺序出现(第一,第二,第三)。

以下代码用于工作并返回我想要的内容(df2)。

[3] df 
[4] objs = [df, pandas.DataFrame(df['Pollutant Levels'].tolist()).iloc[:, :3]]
[5] df2 = pandas.concat(objs, axis=1).drop('Pollutant Levels', axis=1)
[6] print(df2)

我上周才运行此代码,并且运行良好。但是现在我的代码坏了,我从第[4]行得到了这个错误:

IndexError: out-of-bounds on slice (end) 

我没有对代码进行任何更改,但是现在出现了错误。我觉得这是由于我的方法不够健壮或不合适。

对于如何将列表的此列拆分为单独的列的任何建议或指导,将不胜感激!

编辑:我认为.tolist()和.apply方法不适用于我的代码,因为它是一个unicode字符串,即:

#My data format 
u{'a': '1', 'b': '2', 'c': '3'}

#and not
{u'a': '1', u'b': '2', u'c': '3'}

数据是从PostgreSQL数据库以这种格式导入的。这个问题有什么帮助或想法吗?有没有办法转换unicode?


我回答的解决方案略有不同,但是您的代码实际上也应该可以正常工作。使用下面的虚拟示例,如果我忽略了iloc零件,则可以使用熊猫0.18.1进行工作
joris

难道部分iloc[:, :3]假设会有3个项目,而最近的数据切片可能只有1个或2个(例如,中没有b类似index 8813)?
dwanderson

Answers:


166

要将字符串转换为实际的dict,可以执行df['Pollutant Levels'].map(eval)。之后,可以使用以下解决方案将dict转换为不同的列。


通过一个小例子,您可以使用.apply(pd.Series)

In [2]: df = pd.DataFrame({'a':[1,2,3], 'b':[{'c':1}, {'d':3}, {'c':5, 'd':6}]})

In [3]: df
Out[3]:
   a                   b
0  1           {u'c': 1}
1  2           {u'd': 3}
2  3  {u'c': 5, u'd': 6}

In [4]: df['b'].apply(pd.Series)
Out[4]:
     c    d
0  1.0  NaN
1  NaN  3.0
2  5.0  6.0

要将其与数据框的其余部分合并,可以concat将其他列与上述结果结合在一起:

In [7]: pd.concat([df.drop(['b'], axis=1), df['b'].apply(pd.Series)], axis=1)
Out[7]:
   a    c    d
0  1  1.0  NaN
1  2  NaN  3.0
2  3  5.0  6.0

使用我的代码,如果我省略了这一iloc部分,这也可以工作:

In [15]: pd.concat([df.drop('b', axis=1), pd.DataFrame(df['b'].tolist())], axis=1)
Out[15]:
   a    c    d
0  1  1.0  NaN
1  2  NaN  3.0
2  3  5.0  6.0

2
我已经使用pd.DataFrame(df[col].tolist())了很长时间,从没想过apply(pd.Series)。非常好。
ayhan '16

1
我现在意识到了问题。.apply(pd.Series)在我的数据集上不起作用,因为整行是一个unicode字符串。它是:u'{'a':'1','b':'2','c':'3'}而不是{u'a':'1',u'b':'2', u'c':'3'},如您的解决方案所示。因此,代码无法将其分为3个可识别的列。
llaffin '16

2
@ayhan实际上,已经对其进行了测试,并且该DataFrame(df['col'].tolist())方法比apply方法要快很多!
joris

3
@llaffin如果是字符串,则可以df[col].map(eval)在将其转换为DataFrame之前将其转换为实际的dict
joris

2
完美的作品,但(多)慢于贡献的莱赫Birek新的解决方案(2019)stackoverflow.com/a/55355928/2721710
drasc

85

我知道这个问题已经很老了,但是我到这里来寻找答案。实际上,现在有一种更好(更快)的方法json_normalize

import pandas as pd

df2 = pd.json_normalize(df['Pollutant Levels'])

这避免了昂贵的应用功能...


4
哇!我一直在Pandas中整天在JSON对象上进行繁琐而令人困惑的Apply函数,然后我偶然发现了这个答案,并认为“没有办法,这不可能那么容易!” 然后我尝试了,确实如此。非常感谢!
Emac

唯一的问题是,没有json,它似乎不会复制到其他列上,这意味着,如果您要规范化一排json值,则必须将其复制并合并在一起,这仍然比我的迭代好得多方法。真是的
Mr.Drew

对于此解决方案,如何动态选择需要归一化的列的列表?我从.json文件中引入的事务数据来自不同的来源,并且嵌套的列并不总是相同的。我一直在试图找到一种方法来创建包含字典但无法解决的列列表
Callum Smyth

5
from pandas.io.json import json_normalize
Ramin Melikov

有没有一种方法可以将前缀应用于最终列?我注意到有类似meta_prefix和的参数record_prefix。虽然,我无法使用我的数据框(最终的数据框在我的情况下是正确的,但我想应用前缀)。
J. Snow

21

尝试以下操作: 从SQL返回的数据必须转换为Dict。 还是 "Pollutant Levels" 现在Pollutants'

   StationID                   Pollutants
0       8809  {"a":"46","b":"3","c":"12"}
1       8810   {"a":"36","b":"5","c":"8"}
2       8811            {"b":"2","c":"7"}
3       8812                   {"c":"11"}
4       8813          {"a":"82","c":"15"}


df2["Pollutants"] = df2["Pollutants"].apply(lambda x : dict(eval(x)) )
df3 = df2["Pollutants"].apply(pd.Series )

    a    b   c
0   46    3  12
1   36    5   8
2  NaN    2   7
3  NaN  NaN  11
4   82  NaN  15


result = pd.concat([df, df3], axis=1).drop('Pollutants', axis=1)
result

   StationID    a    b   c
0       8809   46    3  12
1       8810   36    5   8
2       8811  NaN    2   7
3       8812  NaN  NaN  11
4       8813   82  NaN  15

13

Merlin的答案更好,更简单,但是我们不需要lambda函数。可以通过以下两种方式之一安全地忽略对字典的评估:

方法1:两步

# step 1: convert the `Pollutants` column to Pandas dataframe series
df_pol_ps = data_df['Pollutants'].apply(pd.Series)

df_pol_ps:
    a   b   c
0   46  3   12
1   36  5   8
2   NaN 2   7
3   NaN NaN 11
4   82  NaN 15

# step 2: concat columns `a, b, c` and drop/remove the `Pollutants` 
df_final = pd.concat([df, df_pol_ps], axis = 1).drop('Pollutants', axis = 1)

df_final:
    StationID   a   b   c
0   8809    46  3   12
1   8810    36  5   8
2   8811    NaN 2   7
3   8812    NaN NaN 11
4   8813    82  NaN 15

方式2:以上两个步骤可以一并组合:

df_final = pd.concat([df, df['Pollutants'].apply(pd.Series)], axis = 1).drop('Pollutants', axis = 1)

df_final:
    StationID   a   b   c
0   8809    46  3   12
1   8810    36  5   8
2   8811    NaN 2   7
3   8812    NaN NaN 11
4   8813    82  NaN 15

13

我强烈建议该方法提取“污染物”列:

df_pollutants = pd.DataFrame(df['Pollutants'].values.tolist(), index=df.index)

它比

df_pollutants = df['Pollutants'].apply(pd.Series)

当df的大小很大时。


如果您能解释这是为什么/为什么会做得更好,那就更好了!对我来说,它总是更快,一旦获得超过1000行,它的速度就快200倍
Sam Mason

@SamMason在apply处理整个数据框时由熊猫管理,但在使用它时,由于它具有纯实现,values因此只能与一起使用numpy ndarrays,速度要快得多c
萨加尔·卡尔

8

你可以用joinpop+ tolist。性能concatdrop+ 相当tolist,但有些人可能会发现此语法更简洁:

res = df.join(pd.DataFrame(df.pop('b').tolist()))

使用其他方法进行基准测试:

df = pd.DataFrame({'a':[1,2,3], 'b':[{'c':1}, {'d':3}, {'c':5, 'd':6}]})

def joris1(df):
    return pd.concat([df.drop('b', axis=1), df['b'].apply(pd.Series)], axis=1)

def joris2(df):
    return pd.concat([df.drop('b', axis=1), pd.DataFrame(df['b'].tolist())], axis=1)

def jpp(df):
    return df.join(pd.DataFrame(df.pop('b').tolist()))

df = pd.concat([df]*1000, ignore_index=True)

%timeit joris1(df.copy())  # 1.33 s per loop
%timeit joris2(df.copy())  # 7.42 ms per loop
%timeit jpp(df.copy())     # 7.68 ms per loop


1

my_df = pd.DataFrame.from_dict(my_dict, orient='index', columns=['my_col'])

..本可以正确解析字典(将每个字典键放入单独的df列中,并将键值放入df行中),因此这些dict首先不会被压入单个列中。


0

我将这些步骤串联在一个方法中,您只需要传递数据框和包含扩展字典的列即可:

def expand_dataframe(dw: pd.DataFrame, column_to_expand: str) -> pd.DataFrame:
    """
    dw: DataFrame with some column which contain a dict to expand
        in columns
    column_to_expand: String with column name of dw
    """
    import pandas as pd

    def convert_to_dict(sequence: str) -> Dict:
        import json
        s = sequence
        json_acceptable_string = s.replace("'", "\"")
        d = json.loads(json_acceptable_string)
        return d    

    expanded_dataframe = pd.concat([dw.drop([column_to_expand], axis=1),
                                    dw[column_to_expand]
                                    .apply(convert_to_dict)
                                    .apply(pd.Series)],
                                    axis=1)
    return expanded_dataframe

-1
df = pd.concat([df['a'], df.b.apply(pd.Series)], axis=1)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.