Разделение словаря/списка внутри столбца Pandas в отдельных столбцах
У меня есть данные, сохраненные в базе данных 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)
Я не внес изменений в код, но теперь получаю ошибку. Я чувствую, что это связано с тем, что мой метод не является надежным или правильным.
Любые предложения или рекомендации о том, как разделить этот столбец списков на отдельные столбцы, будут оценены по достоинству!
EDIT: Я думаю, что методы .tolist() и .apply не работают над моим кодом, потому что это одна строка в Юникоде, т.е.:
#My data format
u{'a': '1', 'b': '2', 'c': '3'}
#and not
{u'a': '1', u'b': '2', u'c': '3'}
Данные импортируются из базы данных postgreSQL в этом формате. Любая помощь или идеи с этой проблемой? есть ли способ конвертировать Юникод?
Ответы
Ответ 1
Чтобы преобразовать строку в фактический 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
Попробуйте следующее: Данные, возвращаемые 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
Ответ 3
Я знаю, что вопрос довольно старый, но я попал сюда в поисках ответов. На самом деле есть лучший (и более быстрый) способ сделать это с помощью json_normalize
:
import pandas as pd
from pandas.io.json import json_normalize
df2 = json_normalize(df['Pollutant Levels'])
Это позволяет избежать дорогостоящих функций применения...
Ответ 4
Ответ Мерлина лучше и супер прост, но нам не нужна лямбда-функция. Оценка словаря может быть безопасно проигнорирована одним из следующих двух способов, как показано ниже:
Способ 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
Ответ 5
Вы можете использовать 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
Ответ 6
Я настоятельно рекомендую метод извлечения столбца "Загрязнители":
df_pollutants = pd.DataFrame(df['Pollutants'].values.tolist(), index=df.index)
это намного быстрее чем
df_pollutants = df['Pollutants'].apply(pd.Series)
когда размер df гигантский.
Ответ 7
Одно из следующих решений:
>> 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
Ответ 8
в одной строке:
df = pd.concat([df['a'], df.b.apply(pd.Series)], axis=1)`