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

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

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

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

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

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

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


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

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.


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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.