Розщеплення словника / списку всередині стовпця Панди на окремі стовпці


147

У мене збережені дані в базі даних 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

Основна проблема, яка у мене виникає, полягає в тому, що списки не мають однакової тривалості. Але всі списки містять до трьох однакових значень: a, b і c. І вони завжди з’являються в одному порядку (перший, 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 методи не працюють на моєму коді, оскільки це одна рядок unicode, тобто:

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

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

Дані імпортуються з бази даних postgreSQL у такому форматі. Будь-яка допомога чи ідеї з цим питанням? чи є спосіб перетворити унікод?


Я відповів дещо іншим рішенням, але ваш код насправді також повинен працювати чудово. Використовуючи мій приклад, що манекен нижче, це працює, використовуючи панди 0.18.1, якщо я залишу ilocчастину
joris

Чи є частиною цього iloc[:, :3]припущення, що буде 3 елементи, а можливо, новіші зрізи даних мають лише 1 або 2 (наприклад, трапляється, що bтаких немає index 8813)?
dwanderson

Відповіді:


167

Щоб перетворити рядок у фактичний дикт, ви можете зробити 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). Дуже хороша.
айхан

1
Зараз я усвідомлюю проблему. .Apply (pd.Series) не працює на моєму наборі даних, оскільки весь рядок - одна рядок Unicode. Це: 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
Працює ідеально, але є (багато) повільніше , ніж нове рішення (2019) , внесеного Леха Birek 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, вам доведеться скопіювати його і об'єднати два, все ще набагато краще, ніж мій ітеративний метод. Кудо!
МістерДрю

як для цього рішення можна було б динамічно вибрати список, які стовпці потребують нормалізації? Дані про трансакцію, які я ввожу з .jsonфайлів, надходять з різних джерел, і це не завжди ті самі стовпці, які вкладені. Я намагався знайти спосіб створити список стовпців, що містять дикти, але, здається, це не виходить
Callum Smyth

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 гігантський.


Було б чудово, якби ви могли пояснити, як / чому це працює, і так набагато краще! для мене це завжди швидше, і ~ 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

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


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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.