Pandas列内の辞書/リストを個別の列に分割する


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)しか含まれていません。そして、それらは常に同じ順序で表示されます(1番目、b 2番目、c 3番目)。

次のコードは、機能し、私が望んでいたもの(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メソッドが1つのユニコード文字列であるため、私のコードでは機能していないと思います、すなわち:

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

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

データはこの形式でpostgreSQLデータベースからインポートされます。この問題に関するヘルプやアイデアはありますか?ユニコードを変換する方法はありますか?


私は少し異なる解決策で答えましたが、あなたのコードも実際にはうまくいくはずです。以下の私のダミーの例を使用すると、ilocパートを省略した場合、これはパンダ0.18.1を使用して動作します
joris

その一部はiloc[:, :3]、3つのアイテムがあり、より最近のデータスライスには1つまたは2つしかないと想定しているのでしょうか(例:のbようなものはないindex 8813
dwanderson

回答:


166

文字列を実際の辞書に変換するには、次のようにします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 2016

1
私は今問題を認識しています。行全体が1つのUnicode文字列であるため、.apply(pd.Series)がデータセットで機能していません。これは、u '{' a ':' 1 '、' b ':' 2 '、' c ':' 3 '}であり、{u'a': '1'、u'b ':' 2 '、ではありません。 u'c ':' 3 '}ソリューションが示すように。したがって、コードはそれを3つの認識可能な列に分割できません。
llaffin

2
@ayhan実際にテストしましたが、このDataFrame(df['col'].tolist())アプローチは適用アプローチよりもかなり高速です!
joris

3
@llaffin文字列の場合df[col].map(eval)、DataFrameに変換する前に実際の辞書に変換できます
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
うわー!私はJSONオブジェクトに対してパンダで面倒で混乱する適用機能を1日中使用していたので、この答えに出くわし、「まさか、そんなに簡単なことはできなかったでしょう!」それから私はそれを試しました、そしてそれはそうでした。本当にありがとう!
Emac

ここでの唯一の問題は、jsonなしで他の列にコピーされないように見えることです。つまり、json値の1行を正規化しようとする場合、それをコピーして2つを結合する必要がありますが、それでも私の反復よりはるかに優れています。方法。賞賛!
Mr.Drew

このソリューションでは、どの列の正規化が必要なリストを動的に選択することができますか?.jsonファイルから取り込んだトランザクションデータは、さまざまなソースからのものであり、ネストされた同じ列とは必ずしも同じではありません。私は、dictsを含むがそれを解決できないように見える列のリストを作成する方法を見つけようとしてきました
Callum Smyth

5
from pandas.io.json import json_normalize
Ramin Melikov

最後の列にプレフィックスを適用する方法はありますか?私のような引数がある気づいたmeta_prefixとはrecord_prefix。私はそれを自分のデータフレームで機能させることはできません(私の場合、最終データフレームは正しいですが、接頭辞を適用したいと思います)。
J.スノー

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

マーリンの答えはより良く、非常に簡単ですが、ラムダ関数は必要ありません。辞書の評価は、以下に示すように、次の2つの方法のいずれかで安全に無視できます。

方法1:2つのステップ

# 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:上記の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のサイズが巨大な場合。


これがどのように/なぜ機能するのかを説明できれば非常に優れています!私にとっては常に高速であり、1,000行を超えると200倍高速になります
Sam Mason

@SamMasonを実行applyすると、データフレーム全体がパンダによって管理されますが、純粋な実装があるため、本質的に高速なものvaluesでのみ再生されます。numpy ndarraysc
Sagar Kar

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

3

1行のソリューションは次のとおりです。

>>> df = pd.concat([df['Station ID'], df['Pollutants'].apply(pd.Series)], axis=1)
>>> print(df)
   Station ID    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

1

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

.. dictを適切に解析し(各dictキーを個別のdf列に入れ、キー値をdf行に入れる)、そのため、最初にdictが1つの列に押しつぶされることはありません。


0

これらの手順をメソッドに連結しました。データフレームと展開するdictを含む列のみを渡す必要があります。

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.