Разделение словаря / списка внутри столбца панд на отдельные столбцы

148

У меня есть данные, сохраненные в базе данных 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?

llaffin
источник
Я ответил немного другим решением, но ваш код тоже должен работать нормально. Используя мой фиктивный пример ниже, это работает с использованием pandas 0.18.1, если я iloc
пропущу
Частично ли iloc[:, :3]предполагается, что будет 3 элемента, и, возможно, более свежие срезы данных имеют только 1 или 2 (например, таких bкак в index 8813)?
dwanderson 06

Ответы:

169

Чтобы преобразовать строку в настоящий dict, вы можете сделать df['Pollutant Levels'].map(eval). Впоследствии приведенное ниже решение можно использовать для преобразования словаря в разные столбцы.


Используя небольшой пример, вы можете использовать .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
1
Теперь я понимаю проблему. .Apply (pd.Series) не работает с моим набором данных, потому что вся строка представляет собой одну строку Unicode. Это: u '{' a ':' 1 ',' b ':' 2 ',' c ':' 3 '}, а не {u'a': '1', u'b ':' 2 ', u'c ':' 3 '}, как показывают ваши решения. Таким образом, код не может разбить его на 3 узнаваемых столбца.
llaffin 06
2
@ayhan На самом деле, протестировал его, и этот DataFrame(df['col'].tolist())подход намного быстрее, чем подход apply!
joris 06
3
@llaffin Если это строка, вы можете преобразовать ее в реальный dict с помощью, df[col].map(eval)прежде чем преобразовывать ее в DataFrame
Джорис
2
Работает отлично, но (намного) медленнее, чем новое решение (2019), предоставленное Лехом Биреком stackoverflow.com/a/55355928/2721710
drasc
85

Я знаю, что вопрос довольно старый, но я пришел сюда в поисках ответов. На самом деле есть лучший (и более быстрый) способ сделать это, используя json_normalize:

import pandas as pd

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

Это позволяет избежать дорогостоящих функций применения ...

Лех Бирек
источник
4
Вот Это Да! Я весь день выполнял утомительные и запутанные функции применения в Pandas для объектов JSON, а затем наткнулся на этот ответ и подумал: «Нет, это не могло быть так просто!» Потом попробовал, и все получилось. Спасибо!
Emac
Единственная проблема здесь заключается в том, что он, похоже, не копирует другие столбцы без json, что означает, что если вы пытаетесь нормализовать одну строку значений json, вам придется скопировать ее и объединить два, все еще намного лучше, чем мой итеративный метод. Cudos!
Мистер Дрю
для этого решения, как можно динамически выбирать список столбцов, нуждающихся в нормализации? Транзакционные данные, которые я привожу из .jsonфайлов, поступают из разных источников, и это не всегда одни и те же вложенные столбцы. Я пытался найти способ создать список столбцов, которые содержат dicts, но, похоже, не могу с этим
справиться
5
from pandas.io.json import json_normalize
Рамин Меликов
Есть ли способ применить префикс к последним столбцам? Я заметил, что есть аргументы вроде meta_prefixи record_prefix. Хотя я не могу заставить эту работу работать с моим фреймворком данных (окончательный фрейм данных в моем случае правильный, но я хотел бы применить префиксы).
Дж. Сноу,
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

Ответ Мерлина лучше и очень просто, но нам не нужна лямбда-функция. Оценка словаря может быть проигнорирована одним из следующих двух способов, как показано ниже:

Способ 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 велик.

user9815968
источник
было бы здорово, если бы вы могли объяснить, как / почему это работает и намного лучше! для меня это всегда быстрее, и в ~ 200 раз быстрее, когда вы получаете более ~ 1000 строк
Сэм Мейсон,
@SamMason, когда вы делаете applyвесь фрейм данных, управляется пандами, но когда дело доходит до valuesнего, он играет только с тем, numpy ndarraysчто по сути быстрее из-за того, что он имеет чистые cреализации.
Сагар Кар
8

Вы можете использовать joinс pop+ tolist. Производительность сопоставима concatс drop+ 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
JPP
источник
3

Однострочное решение следующее:

>>> 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), поэтому dicts не будет сжиматься в один столбец в первую очередь.

mirekphd
источник
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
Эмануэль Фонтеллес
источник
-1
df = pd.concat([df['a'], df.b.apply(pd.Series)], axis=1)
Сирадж С.
источник