Pandy warunkowe tworzenie kolumny serii / ramki danych

314

Mam ramkę danych w następujący sposób:

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

Chcę dodać kolejną kolumnę do ramki danych (lub wygenerować serię) o tej samej długości co ramka danych (= równa liczba rekordów / wierszy), która ustawia kolor zielony, jeśli zestaw = „Z” i „czerwony”, jeśli zestaw = inaczej .

Jak najlepiej to zrobić?

użytkownik7289
źródło

Odpowiedzi:

711

Jeśli masz tylko dwie opcje do wyboru:

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

Na przykład,

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)

daje

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

Jeśli masz więcej niż dwa warunki, użyjnp.select . Na przykład, jeśli chcesz colorbyć

  • yellow kiedy (df['Set'] == 'Z') & (df['Type'] == 'A')
  • w przeciwnym razie bluekiedy(df['Set'] == 'Z') & (df['Type'] == 'B')
  • w przeciwnym razie purplekiedy(df['Type'] == 'B')
  • w przeciwnym razie black,

następnie użyj

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)

co daje

  Set Type   color
0   Z    A  yellow
1   Z    B    blue
2   X    B  purple
3   Y    C   black
unutbu
źródło
1
nie działa, jeśli wstawię dwa warunki, w których klauzula
zi
2
df ['color'] = lista (np. gdzie (df ['Set'] == 'Z', 'green', 'red')) wyciszy ostrzeżenie o pandach: Próbuje się ustawić wartość na kopii wycinka z DataFrame. Spróbuj zamiast tego użyć .loc [row_indexer, col_indexer] = wartość
denson,
3
„zielony” i „czerwony” można również zastąpić arytmetyką kolumny. np ,df['foo'] = np.where(df['Set']=='Z', df['Set'], df['Type'].shift(1))
Alejandro
czy np.where tworzy nową kolumnę? Użyłem tego kodu i kiedy robię df.color.head (), dostaję: obiekt „numpy.ndarray” nie ma atrybutu „head”
vvv
3
Szkoda, że ​​nie mogę tego wielokrotnie głosować. Jedno głosowanie wydaje się niewystarczające.
Harper
120

Zrozumienie listy jest innym sposobem warunkowego utworzenia kolejnej kolumny. Jeśli pracujesz z typami obiektów w kolumnach, tak jak w twoim przykładzie, wyliczenia list zwykle przewyższają większość innych metod.

Przykładowe zrozumienie listy:

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

Testy% czasu:

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
bezczelny drań
źródło
4
Zauważ, że przy znacznie większych ramkach danych (think pd.DataFrame({'Type':list('ABBC')*100000, 'Set':list('ZZXY')*100000})-size) numpy.wherewyprzedza map, ale lista jest królem (około 50% szybciej niż numpy.where).
blacksite
3
Czy można zastosować metodę zrozumienia listy, jeśli warunek wymaga informacji z wielu kolumn? Szukam czegoś takiego (to nie działa):df['color'] = ['red' if (x['Set'] == 'Z') & (x['Type'] == 'B') else 'green' for x in df]
Mappi
2
Dodaj iterrows do ramki danych, aby uzyskać dostęp do wielu kolumn za pośrednictwem wiersza: ['red' if (row ['Set'] == 'Z') & (row ['Type'] == 'B') else 'green 'dla indeksu, wiersz w df.iterrows ()]
cheekybastard
1
Pamiętaj, że to miłe rozwiązanie nie będzie działać, jeśli będziesz musiał pobrać wartości zastępcze z innej serii w ramce danych, na przykładdf['color_type'] = np.where(df['Set']=='Z', 'green', df['Type'])
Paul Rougieux,
@cheekybastard Albo nie, ponieważ .iterrows()notorycznie jest powolny i DataFrame nie powinien być modyfikowany podczas iteracji.
AMC
21

Innym sposobem, w jaki można to osiągnąć, jest

df['color'] = df.Set.map( lambda x: 'red' if x == 'Z' else 'green')
acharuva
źródło
Dobre podejście, można to zapamiętać dla większej wydajności (w większych zestawach danych), choć wymagałoby to dodatkowego kroku.
Yaakov Bressler,
21

Oto jeszcze jeden sposób na skórowanie tego kota, używając słownika do mapowania nowych wartości na klucze na liście:

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

Jak to wygląda:

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

To podejście może być bardzo skuteczne, gdy trzeba wykonać wiele ifelseinstrukcji typu (tj. Wiele unikalnych wartości do zastąpienia).

I oczywiście zawsze możesz to zrobić:

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

Ale takie podejście jest ponad trzy razy wolniejsze niż applypodejście z góry na mojej maszynie.

Możesz to również zrobić, używając dict.get:

df['NEW_VALUE'] = [values_dict.get(v, None) for v in df['INDICATOR']]
blacksite
źródło
Podoba mi się ta odpowiedź, ponieważ pokazuje, jak wykonać wielokrotne zamiany wartości
Monica Heddneck
Ale to podejście jest ponad trzy razy wolniejsze niż podejście z góry na moim komputerze. Jak je porównałeś? Z moich szybkich pomiarów .map()rozwiązanie jest ~ 10 razy szybsze niż .apply().
AMC
Aktualizacja: w 100 000 000 wierszy 52 wartości ciągu .apply()zajmuje 47 sekund, w porównaniu z jedynie 5,91 sekundy .map().
AMC
19

Poniższe jest wolniejsze niż w tym przypadku podejścia tutaj , ale możemy obliczyć dodatkową kolumnę na podstawie zawartości więcej niż jednej kolumny, a dla dodatkowej kolumny można obliczyć więcej niż dwie wartości.

Prosty przykład wykorzystujący tylko kolumnę „Ustaw”:

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

Przykład z większą ilością kolorów i większej liczby kolumn branych pod uwagę:

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

Edycja (21.06.2019): Za pomocą plydata

Możliwe jest również użycie plydata do robienia tego rodzaju rzeczy (wydaje się to nawet wolniejsze niż używanie assigni apply, chociaż).

from plydata import define, if_else

Proste 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

Zagnieżdżone 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
bli
źródło
10

Być może było to możliwe dzięki nowszym aktualizacjom Pand, ale myślę, że poniższe są najkrótszą i być może najlepszą odpowiedzią na pytanie. Możesz użyć tej .locmetody i użyć jednego lub kilku warunków, w zależności od potrzeb.

Podsumowanie kodu:

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"

Wyjaśnienie:

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

dodaj kolumnę „kolor” i ustaw wszystkie wartości na „czerwony”

df['Color'] = "red"

Zastosuj swój pojedynczy warunek:

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

lub wiele warunków, jeśli chcesz:

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

Możesz przeczytać o operatorach logicznych Pandas i wyborze warunkowym tutaj: Operatory logiczne do indeksowania wartości logicznych w Pandach

Hossein
źródło
2
Jak dotąd najlepszy. Prawdopodobnie możesz dodać więcej warunków, które byłyby kodemdf.loc[(df['Set']=="Z") & (df['Type']=="A"), 'Color'] = "green"
Salvador Vigo
2
To powinna być zaakceptowana odpowiedź. Właściwie idiomatyczny i rozszerzalny.
AMC
1

Jedna linijka z .apply()metodą jest następująca:

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

Następnie dframka danych wygląda następująco:

>>> print(df)
  Type Set  color
0    A   Z  green
1    B   Z  green
2    B   X    red
3    C   Y    red
Jaroslav Bezděk
źródło
0

Jeśli pracujesz z ogromnymi danymi, najlepiej byłoby zapamiętać podejście:

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

Takie podejście będzie najszybsze, gdy będziesz mieć wiele powtarzanych wartości. Moją ogólną zasadą jest zapamiętywanie, kiedy: data_size> 10**4& n_distinct<data_size/4

Ex Zapamiętaj w skrzynce 10 000 wierszy z 2500 lub mniej wyraźnymi wartościami.

Yaakov Bressler
źródło
W porządku, więc z tylko 2 odrębnymi wartościami do mapowania, 100 000 000 wierszy, uruchomienie 6,67 sekundy bez „zapamiętywania” i 9,86 sekundy z.
AMC
100 000 000 wierszy, 52 różne wartości, przy czym 1 z nich odwzorowuje na pierwszą wartość wyjściową, a pozostałe 51 wszystkie odpowiadają drugiej: 7,99 sekundy bez zapamiętywania, 11,1 sekundy z.
AMC
Czy twoje wartości są w losowej kolejności? A może są z powrotem do tyłu? Wysoka prędkość pand może być spowodowana buforowaniem @AMC
Yaakov Bressler
1
Czy twoje wartości są w losowej kolejności? A może są z powrotem do tyłu? Wartości są losowe, wybierane za pomocą random.choices().
AMC