Pandas условное создание столбца серии / dataframe


314

У меня есть датафрейм по линии ниже:

    Type       Set
1    A          Z
2    B          Z           
3    B          X
4    C          Y

Я хочу добавить еще один столбец к фрейму данных (или сгенерировать серию) той же длины, что и фрейм данных (= равное количество записей / строк), который устанавливает зеленый цвет, если Set = 'Z', и 'красный', если Set = иначе ,

Какой лучший способ сделать это?

Ответы:


713

Если у вас есть только два варианта на выбор:

df['color'] = np.where(df['Set']=='Z', 'green', 'red')

Например,

import pandas as pd
import numpy as np

df = pd.DataFrame({'Type':list('ABBC'), 'Set':list('ZZXY')})
df['color'] = np.where(df['Set']=='Z', 'green', 'red')
print(df)

доходность

  Set Type  color
0   Z    A  green
1   Z    B  green
2   X    B    red
3   Y    C    red

Если у вас более двух условий, используйтеnp.select . Например, если вы хотите colorбыть

  • yellow когда (df['Set'] == 'Z') & (df['Type'] == 'A')
  • в противном случае, blueкогда(df['Set'] == 'Z') & (df['Type'] == 'B')
  • в противном случае, purpleкогда(df['Type'] == 'B')
  • в противном случае black,

затем используйте

df = pd.DataFrame({'Type':list('ABBC'), 'Set':list('ZZXY')})
conditions = [
    (df['Set'] == 'Z') & (df['Type'] == 'A'),
    (df['Set'] == 'Z') & (df['Type'] == 'B'),
    (df['Type'] == 'B')]
choices = ['yellow', 'blue', 'purple']
df['color'] = np.select(conditions, choices, default='black')
print(df)

что дает

  Set Type   color
0   Z    A  yellow
1   Z    B    blue
2   X    B  purple
3   Y    C   black

1
не работает, если я поставлю два условия внутри, где оговорка с и
Amol Sharma

2
df ['color'] = list (np.where (df ['Set'] == 'Z', 'green', 'red')) отключит предупреждение панд: значение пытается быть установлено на копии среза из фрейма данных. Попробуйте вместо этого использовать .loc [row_indexer, col_indexer] = value
denson

3
«зеленый» и «красный» также можно заменить на арифметику столбцов. например ,df['foo'] = np.where(df['Set']=='Z', df['Set'], df['Type'].shift(1))
Алехандро

np.where создает новый столбец? Я использовал этот код, и когда я делаю df.color.head (), я получаю: у объекта 'numpy.ndarray' нет атрибута 'head'
vvv

3
Обидно, я не могу поднять это несколько раз. Одного голоса недостаточно.
Харпер

120

Понимание списка - это еще один способ условно создать другой столбец. Если вы работаете с dtypes объекта в столбцах, как в вашем примере, то списочная обработка обычно превосходит большинство других методов.

Пример списка понимания:

df['color'] = ['red' if x == 'Z' else 'green' for x in df['Set']]

% timeit тесты:

import pandas as pd
import numpy as np

df = pd.DataFrame({'Type':list('ABBC'), 'Set':list('ZZXY')})
%timeit df['color'] = ['red' if x == 'Z' else 'green' for x in df['Set']]
%timeit df['color'] = np.where(df['Set']=='Z', 'green', 'red')
%timeit df['color'] = df.Set.map( lambda x: 'red' if x == 'Z' else 'green')

1000 loops, best of 3: 239 µs per loop
1000 loops, best of 3: 523 µs per loop
1000 loops, best of 3: 263 µs per loop

4
Обратите внимание, что с гораздо большими фреймами данных (думаю- pd.DataFrame({'Type':list('ABBC')*100000, 'Set':list('ZZXY')*100000})размер) numpy.whereопережает map, но понимание списка является королем (примерно на 50% быстрее, чем numpy.where).
Blacksite

3
Можно ли использовать метод понимания списка, если условие требует информацию из нескольких столбцов? Я ищу что-то вроде этого (это не работает):df['color'] = ['red' if (x['Set'] == 'Z') & (x['Type'] == 'B') else 'green' for x in df]
Mappi

2
Добавьте иттерроу к кадру данных, и вы сможете получить доступ к нескольким столбцам через строку: ['red' if (row ['Set'] == 'Z') & (row ['Type'] == 'B') else 'green 'для индекса, введите в df.iterrows ()]
cheekybastard

1
Обратите внимание, что это хорошее решение не будет работать, если вам нужно взять заменяющие значения из других рядов во фрейме данных, таких какdf['color_type'] = np.where(df['Set']=='Z', 'green', df['Type'])
Paul Rougieux

@cheekybastard Или нет, так .iterrows()как общеизвестно вялый и DataFrame не должны быть изменены во время итерации.
AMC

21

Другим способом достижения этого является

df['color'] = df.Set.map( lambda x: 'red' if x == 'Z' else 'green')

Хороший подход, это можно запомнить для более быстрой эффективности (в больших наборах данных), хотя потребует дополнительного шага.
Яаков Бресслер

21

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

def map_values(row, values_dict):
    return values_dict[row]

values_dict = {'A': 1, 'B': 2, 'C': 3, 'D': 4}

df = pd.DataFrame({'INDICATOR': ['A', 'B', 'C', 'D'], 'VALUE': [10, 9, 8, 7]})

df['NEW_VALUE'] = df['INDICATOR'].apply(map_values, args = (values_dict,))

Как это выглядит:

df
Out[2]: 
  INDICATOR  VALUE  NEW_VALUE
0         A     10          1
1         B      9          2
2         C      8          3
3         D      7          4

Этот подход может быть очень мощным, когда вам нужно сделать много ifelseоператоров типа (т.е. множество уникальных значений для замены).

И, конечно, вы всегда можете сделать это:

df['NEW_VALUE'] = df['INDICATOR'].map(values_dict)

Но этот подход более чем в три раза медленнее, чем applyподход сверху, на моей машине.

И вы также можете сделать это, используя dict.get:

df['NEW_VALUE'] = [values_dict.get(v, None) for v in df['INDICATOR']]

Мне нравится этот ответ, потому что он показывает, как сделать множественные замены значений
Моника Хедднек

Но этот подход более чем в три раза медленнее, чем применяемый подход сверху, на моей машине. Как вы оценили это? Из моих быстрых измерений, .map()решение в ~ 10 раз быстрее, чем .apply().
AMC

Обновление: на 100 000 000 строк, 52 строковых значения, .apply()требуется 47 секунд, по сравнению с 5,91 секундами .map().
AMC

19

Следующее является более медленным, чем подходы, рассчитанные здесь , но мы можем вычислить дополнительный столбец на основе содержимого более чем одного столбца, и для дополнительного столбца можно вычислить более двух значений.

Простой пример с использованием только столбца «Set»:

def set_color(row):
    if row["Set"] == "Z":
        return "red"
    else:
        return "green"

df = df.assign(color=df.apply(set_color, axis=1))

print(df)
  Set Type  color
0   Z    A    red
1   Z    B    red
2   X    B  green
3   Y    C  green

Пример с большим количеством цветов и большим количеством столбцов:

def set_color(row):
    if row["Set"] == "Z":
        return "red"
    elif row["Type"] == "C":
        return "blue"
    else:
        return "green"

df = df.assign(color=df.apply(set_color, axis=1))

print(df)
  Set Type  color
0   Z    A    red
1   Z    B    red
2   X    B  green
3   Y    C   blue

Изменить (21/06/2019): Использование plydata

Также возможно использовать plydata для таких вещей (хотя это кажется даже медленнее, чем использование assignи apply).

from plydata import define, if_else

Просто if_else:

df = define(df, color=if_else('Set=="Z"', '"red"', '"green"'))

print(df)
  Set Type  color
0   Z    A    red
1   Z    B    red
2   X    B  green
3   Y    C  green

Вложенный if_else:

df = define(df, color=if_else(
    'Set=="Z"',
    '"red"',
    if_else('Type=="C"', '"green"', '"blue"')))

print(df)                            
  Set Type  color
0   Z    A    red
1   Z    B    red
2   X    B   blue
3   Y    C  green

10

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

Сводка кода:

df=pd.DataFrame(dict(Type='A B B C'.split(), Set='Z Z X Y'.split()))
df['Color'] = "red"
df.loc[(df['Set']=="Z"), 'Color'] = "green"

#practice!
df.loc[(df['Set']=="Z")&(df['Type']=="B")|(df['Type']=="C"), 'Color'] = "purple"

Объяснение:

df=pd.DataFrame(dict(Type='A B B C'.split(), Set='Z Z X Y'.split()))

# df so far: 
  Type Set  
0    A   Z 
1    B   Z 
2    B   X 
3    C   Y

добавить столбец «color» и установить все значения в «red»

df['Color'] = "red"

Примените ваше единственное условие:

df.loc[(df['Set']=="Z"), 'Color'] = "green"


# df: 
  Type Set  Color
0    A   Z  green
1    B   Z  green
2    B   X    red
3    C   Y    red

или несколько условий, если вы хотите:

df.loc[(df['Set']=="Z")&(df['Type']=="B")|(df['Type']=="C"), 'Color'] = "purple"

Вы можете прочитать о логических операторах Pandas и условном выборе здесь: Логические операторы для логического индексирования в Pandas


2
Лучший на данный момент. Вы могли бы, вероятно, добавить для большего количества условий, которые будут кодdf.loc[(df['Set']=="Z") & (df['Type']=="A"), 'Color'] = "green"
Сальвадор Виго

2
Это должен быть принятый ответ. На самом деле идиоматичный и расширяемый.
AMC

1

Один вкладыш с .apply()методом следующий:

df['color'] = df['Set'].apply(lambda set_: 'green' if set_=='Z' else 'red')

После этого dfфрейм данных выглядит так:

>>> print(df)
  Type Set  color
0    A   Z  green
1    B   Z  green
2    B   X    red
3    C   Y    red

0

Если вы работаете с массивными данными, лучше всего использовать запомненный подход:

# First create a dictionary of manually stored values
color_dict = {'Z':'red'}

# Second, build a dictionary of "other" values
color_dict_other = {x:'green' for x in df['Set'].unique() if x not in color_dict.keys()}

# Next, merge the two
color_dict.update(color_dict_other)

# Finally, map it to your column
df['color'] = df['Set'].map(color_dict)

Этот подход будет самым быстрым, когда у вас много повторяющихся значений. Мое общее правило - помнить, когда: data_size> 10**4& n_distinct<data_size/4

Ex Memoize в случае 10000 строк с 2500 или менее различными значениями.


Итак, при отображении только 2-х различных значений, 100 000 000 строк, требуется 6,67 секунды, чтобы работать без «запоминания», и 9,86 секунды с.
AMC

100 000 000 строк, 52 различных значения, где 1 из этих значений соответствует первому выходному значению, а остальные 51 соответствуют другому: 7,99 секунды без запоминания, 11,1 секунды с.
AMC

Ваши значения в случайном порядке? Или они спиной к спине? Высокая скорость работы панд может быть связана с кэшированием @AMC
Яаков Бресслер

1
Ваши значения в случайном порядке? Или они спиной к спине? Значения случайные, выбираются с помощью random.choices().
AMC
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.