У меня есть данные, сохраненные в базе данных postgreSQL. Я запрашиваю эти данные с помощью Python2.7 и превращаю их в DataFrame Pandas. Однако в последнем столбце этого фрейма данных есть словарь значений. DataFrame 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 `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'. И они всегда появляются в одном и том же порядке ('a' первый, 'b' второй, 'c' третий).

Следующий код ИСПОЛЬЗУЕТСЯ для работы и возврата именно того, что я хотел (df2).

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

Я запускал этот код только на прошлой неделе, и он работал нормально. Но теперь мой код не работает, и я получаю эту ошибку из строки [4]: ​​

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

Я не вносил изменений в код, но теперь получаю сообщение об ошибке. Я считаю, что это связано с тем, что мой метод не является надежным или правильным.

Любые предложения или инструкции о том, как разделить этот столбец списков на отдельные столбцы, были бы очень признательны!

РЕДАКТИРОВАТЬ: я думаю, что методы .tolist () и .apply не работают с моим кодом, потому что это одна строка Unicode, то есть: * 100004*

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

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

Данные импортируются из базы данных postgreSQL в этом формате. Любая помощь или идеи по этой проблеме? есть ли способ конвертировать Unicode?

Ответы (13)

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

.. правильно проанализировал бы dict (поместив каждый ключ dict в отдельный столбец df, а значения ключей - в строки df), так что dicts не будет сжиматься в один столбец в первую очередь.

  • Самый быстрый метод нормализации столбца с плоскими одноуровневыми dictsсогласно временному анализу, выполненному Shijith в этом ответе:
    • df.join (pd.DataFrame (df.pop ('Pollutants'). Values.tolist ()))
    • Это не решит другие проблемы со столбцами list или dicts, которые указаны ниже, например, строки с NaNили вложенные dicts.
  1. pd.json_normalize (df.Pollutants) значительно быстрее, чем df.Pollutants.apply (pd.Series)
    • См. %% timeit ниже. Для 1M строк .json_normalize в 47 раз быстрее, чем .apply.
  2. При чтении данных из файла или из объекта, возвращаемого базой данных или API, может быть непонятно, имеет ли столбец dict dict или str тип.
    • Если словари в столбце имеют тип str, они должны быть преобразованы обратно в тип dict, используя ast.literal_evalили json.loads (…).
  3. Используйте pd.json_normalize для преобразования dictsс ключами в качестве заголовков и значениями для строк .
    • Существуют дополнительные параметры (например, record_path & meta) для работы с вложенными dicts.
  4. Используйте pandas.DataFrame.join, чтобы объединить исходный DataFrame, df, со столбцами, созданными с использованием pd.json_normalize
    • Если индекс не является целым числом (как в примере), сначала используйте df.reset_index (), чтобы получить индекс целых чисел, прежде чем выполнять нормализацию и присоединяйтесь.
  5. Наконец, используйте pandas.DataFrame.drop, чтобы удалить ненужный столбец dicts
  • В качестве примечания, если в столбце есть какие-либо NaN, они должны быть заполнены пустым dict
    • df.Pollutants = df.Pollutants.fillna ({i: {} для i в df.index})
импортировать панды как pd
from ast import literal_eval
импортировать numpy как np

data = {'ID станции': [8809, 8810, 8811, 8812, 8813, 8814],
        'Загрязняющие вещества': ['{"a": "46", "b": "3", "c": "12"}', '{"a": "36", "b": "5" , "c": "8"} ',' {"b": "2", "c": "7"} ',' {"c": "11"} ',' {"a": " 82 "," c ":" 15 "} ', np.nan]}

df = pd.DataFrame (данные)

# дисплей (df)
   ID станции Загрязняющие вещества
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"}
5 8814 NaN

# замените NaN на '{}', если столбец представляет собой строки, в противном случае замените на {}
# df.Pollutants = df.Pollutants.fillna ('{}') # если NaN находится в столбце строк
df.Pollutants = df.Pollutants.fillna ({i: {} for i in df.index}) # если столбец не является строкой

# Преобразуйте столбец строковых диктовок в диктовки
# пропустить эту строку, если столбец содержит dicts
df.Pollutants = df.Pollutants.apply (literal_eval)

# сбросить индекс, если индекс не является уникальным целым числом от 0 до n-1
# df.reset_index (inplace = True) # при необходимости раскомментируйте

# нормализуем столбец словарей и присоединяем его к df
df = df.join (pd.json_normalize (df.Pollutants))

# drop Загрязняющие вещества
df.drop (columns = ['Pollutants'], inplace = True)

# дисплей (df)
   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
5 8814 NaN NaN NaN

%% timeit

# фрейм данных с 1 млн строк
dfb = pd.concat ([df] * 200000) .reset_index (drop = True)

%% timeit
dfb.join (pd.json_normalize (dfb.Pollutants))
[вне]:
5,44 с ± 32,3 мс на цикл (среднее ± стандартное отклонение из 7 прогонов, по 1 циклу в каждом)

%% timeit
pd.concat ([dfb.drop (columns = ['Pollutants']), dfb.Pollutants.apply (pd.Series)], axis = 1)
[вне]:
4 мин 17 с ± 2,44 с на цикл (среднее ± стандартное отклонение из 7 прогонов, по 1 петле в каждом)

Чтобы преобразовать строку в реальный dict, вы можете сделать df ['Уровни загрязнения']. 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

Чтобы объединить его с остальной частью фрейма данных, вы можете объединить другие столбцы с указанным выше результатом:

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
df = pd.concat([df['a'], df.b.apply(pd.Series)], axis=1)

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

Способ 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

Вы можете использовать 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

Как разбить столбец словарей на отдельные столбцы с помощью панд?

pd.DataFrame (df ['val']. Tolist ()) - канонический метод расчленения столбца словарей

Вот ваше доказательство с использованием цветного графика.

enter image description here

Контрольный код для справки.

Обратите внимание, что я только рассчитываю время взрыва, поскольку это самая интересная часть ответа на этот вопрос - другие аспекты построения результата (например, использовать ли pop или drop). имеет отношение к обсуждению и может быть проигнорировано (однако следует отметить, что использование pop позволяет избежать последующего вызова drop, поэтому окончательное решение будет немного более производительным, но мы все еще слушаем столбец и передав его в pd.DataFrame в любом случае).

Кроме того, pop деструктивно изменяет входной DataFrame, что затрудняет запуск в тестовом коде, который предполагает, что входные данные не меняются во время выполнения тестов.


Критика других решений

  • df ['val']. Apply (pd.Series) работает очень медленно для больших N, поскольку pandas создает объекты Series для каждой строки, а затем переходит к созданию DataFrame из них. При большем N производительность падает до минут или часов.

  • pd.json_normalize (df ['val'])) медленнее просто потому, что json_normalize предназначен для работы с гораздо более сложными входными данными - особенно глубоко вложенными JSON с несколькими путями записи и метаданными. У нас есть простой плоский dict, для которого достаточно pd.DataFrame, поэтому используйте его, если ваши dicts плоские.

  • В некоторых ответах предлагается df.pop ('val'). Values.tolist () или df.pop ('val'). To_numpy (). Tolist (). Я не думаю, что это имеет большое значение, просматриваете ли вы серию или массив numpy. Это на одну операцию меньше, чтобы просмотреть серию напрямую, и действительно не медленнее, поэтому я бы рекомендовал избегать создания массива numpy на промежуточном этапе.

Настоятельно рекомендую метод извлечения столбца «Загрязняющие вещества»:

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

намного быстрее, чем

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

при гигантском размере df.

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

>>> 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

Попробуйте следующее: Данные, возвращаемые из SQL, должны быть преобразованы в Dict. или может быть «Уровни загрязнителей» теперь Загрязняющие вещества '

   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

Я объединил эти шаги в методе, вам нужно передать только фрейм данных и столбец, содержащий расширяемый 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
>>> df

   Station ID                        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"}

сравнение скорости для большого набора данных из 10 миллионов строк

>>> df = pd.concat([df]*100000).reset_index(drop=True)
>>> df = pd.concat([df]*20).reset_index(drop=True)
>>> print(df.shape)
(10000000, 2)
def apply_drop(df):
    return df.join(df['Pollutants'].apply(pd.Series)).drop('Pollutants', axis=1)  

def json_normalise_drop(df):
    return df.join(pd.json_normalize(df.Pollutants)).drop('Pollutants', axis=1)  

def tolist_drop(df):
    return df.join(pd.DataFrame(df['Pollutants'].tolist())).drop('Pollutants', axis=1)  

def vlues_tolist_drop(df):
    return df.join(pd.DataFrame(df['Pollutants'].values.tolist())).drop('Pollutants', axis=1)  

def pop_tolist(df):
    return df.join(pd.DataFrame(df.pop('Pollutants').tolist()))  

def pop_values_tolist(df):
    return df.join(pd.DataFrame(df.pop('Pollutants').values.tolist()))

>>> %timeit apply_drop(df.copy())
1 loop, best of 3: 53min 20s per loop
>>> %timeit json_normalise_drop(df.copy())
1 loop, best of 3: 54.9 s per loop
>>> %timeit tolist_drop(df.copy())
1 loop, best of 3: 6.62 s per loop
>>> %timeit vlues_tolist_drop(df.copy())
1 loop, best of 3: 6.63 s per loop
>>> %timeit pop_tolist(df.copy())
1 loop, best of 3: 5.99 s per loop
>>> %timeit pop_values_tolist(df.copy())
1 loop, best of 3: 5.94 s per loop
+---------------------+-----------+
| apply_drop          | 53min 20s |
| json_normalise_drop |    54.9 s |
| tolist_drop         |    6.62 s |
| vlues_tolist_drop   |    6.63 s |
| pop_tolist          |    5.99 s |
| pop_values_tolist   |    5.94 s |
+---------------------+-----------+

df.join (pd.DataFrame (df.pop ('Pollutants'). Values.tolist ())) самый быстрый

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

import pandas as pd

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

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

2022 WebDevInsider