pandy tworzą nową kolumnę na podstawie wartości z innych kolumn / stosują funkcję wielu kolumn, wierszowo

316

Chcę zastosować moją funkcję niestandardową (używa się if-else drabinę) do tych sześciu kolumn ( ERI_Hispanic, ERI_AmerInd_AKNatv, ERI_Asian, ERI_Black_Afr.Amer, ERI_HI_PacIsl,ERI_White ) w każdym rzędzie moim dataframe.

Próbowałem różnych metod z innych pytań, ale nadal nie mogę znaleźć właściwej odpowiedzi na mój problem. Najważniejsze jest to, że jeśli dana osoba jest latynoska, nie można jej liczyć jako niczego innego. Nawet jeśli mają „1” w innej kolumnie etnicznej, nadal są liczone jako Hiszpanie, a nie dwie lub więcej ras. Podobnie, jeśli suma wszystkich kolumn ERI jest większa niż 1, są one liczone jako dwie lub więcej ras i nie mogą być liczone jako unikalna pochodzenie etniczne (z wyjątkiem Latynosów). Mam nadzieję, że ma to sens. Każda pomoc będzie mile widziana.

To prawie jak wykonywanie pętli for przez każdy wiersz i jeśli każdy rekord spełnia kryterium, są one dodawane do jednej listy i usuwane z oryginału.

Na podstawie poniższej ramki danych muszę obliczyć nową kolumnę na podstawie następującej specyfikacji SQL:

========================= KRYTERIA ======================== =======

IF [ERI_Hispanic] = 1 THEN RETURN Hispanic
ELSE IF SUM([ERI_AmerInd_AKNatv] + [ERI_Asian] + [ERI_Black_Afr.Amer] + [ERI_HI_PacIsl] + [ERI_White]) > 1 THEN RETURN Two or More
ELSE IF [ERI_AmerInd_AKNatv] = 1 THEN RETURN A/I AK Native
ELSE IF [ERI_Asian] = 1 THEN RETURN Asian
ELSE IF [ERI_Black_Afr.Amer] = 1 THEN RETURN Black/AA
ELSE IF [ERI_HI_PacIsl] = 1 THEN RETURN Haw/Pac Isl.”
ELSE IF [ERI_White] = 1 THEN RETURN White

Komentarz: Jeśli flaga ERI dla języka hiszpańskiego jest prawdziwa (1), pracownik jest klasyfikowany jako „hiszpański”

Komentarz: Jeśli więcej niż 1 nie-hiszpańska flaga ERI jest prawdziwa, zwróć „dwa lub więcej”

====================== DATAFRAME ===========================

     lname          fname       rno_cd  eri_afr_amer    eri_asian   eri_hawaiian    eri_hispanic    eri_nat_amer    eri_white   rno_defined
0    MOST           JEFF        E       0               0           0               0               0               1           White
1    CRUISE         TOM         E       0               0           0               1               0               0           White
2    DEPP           JOHNNY              0               0           0               0               0               1           Unknown
3    DICAP          LEO                 0               0           0               0               0               1           Unknown
4    BRANDO         MARLON      E       0               0           0               0               0               0           White
5    HANKS          TOM         0                       0           0               0               0               1           Unknown
6    DENIRO         ROBERT      E       0               1           0               0               0               1           White
7    PACINO         AL          E       0               0           0               0               0               1           White
8    WILLIAMS       ROBIN       E       0               0           1               0               0               0           White
9    EASTWOOD       CLINT       E       0               0           0               0               0               1           White
Dave
źródło
Twoja szczególna funkcja to po prostu długa drabina jeśli-inaczej, gdzie wartości niektórych zmiennych mają pierwszeństwo przed innymi. Można by go nazwać dekoderem priorytetowym w mowie inżynierii sprzętowej.
smci

Odpowiedzi:

407

OK, dwa kroki do tego - najpierw jest napisanie funkcji wykonującej tłumaczenie, które chcesz - podałem przykład na podstawie twojego pseudokodu:

def label_race (row):
   if row['eri_hispanic'] == 1 :
      return 'Hispanic'
   if row['eri_afr_amer'] + row['eri_asian'] + row['eri_hawaiian'] + row['eri_nat_amer'] + row['eri_white'] > 1 :
      return 'Two Or More'
   if row['eri_nat_amer'] == 1 :
      return 'A/I AK Native'
   if row['eri_asian'] == 1:
      return 'Asian'
   if row['eri_afr_amer']  == 1:
      return 'Black/AA'
   if row['eri_hawaiian'] == 1:
      return 'Haw/Pac Isl.'
   if row['eri_white'] == 1:
      return 'White'
   return 'Other'

Być może warto to omówić, ale wydaje się, że to załatwia sprawę - zauważ, że parametr wchodzący w funkcję jest uważany za obiekt serii oznaczony jako „wiersz”.

Następnie użyj funkcji Apply w pandach, aby zastosować funkcję - np

df.apply (lambda row: label_race(row), axis=1)

Zwróć uwagę na specyfikator oś = 1, co oznacza, że ​​aplikacja jest wykonywana w wierszu, a nie na poziomie kolumny. Wyniki są tutaj:

0           White
1        Hispanic
2           White
3           White
4           Other
5           White
6     Two Or More
7           White
8    Haw/Pac Isl.
9           White

Jeśli jesteś zadowolony z tych wyników, uruchom go ponownie, zapisując wyniki w nowej kolumnie w oryginalnej ramce danych.

df['race_label'] = df.apply (lambda row: label_race(row), axis=1)

Powstała ramka danych wygląda następująco (przewiń w prawo, aby zobaczyć nową kolumnę):

      lname   fname rno_cd  eri_afr_amer  eri_asian  eri_hawaiian   eri_hispanic  eri_nat_amer  eri_white rno_defined    race_label
0      MOST    JEFF      E             0          0             0              0             0          1       White         White
1    CRUISE     TOM      E             0          0             0              1             0          0       White      Hispanic
2      DEPP  JOHNNY    NaN             0          0             0              0             0          1     Unknown         White
3     DICAP     LEO    NaN             0          0             0              0             0          1     Unknown         White
4    BRANDO  MARLON      E             0          0             0              0             0          0       White         Other
5     HANKS     TOM    NaN             0          0             0              0             0          1     Unknown         White
6    DENIRO  ROBERT      E             0          1             0              0             0          1       White   Two Or More
7    PACINO      AL      E             0          0             0              0             0          1       White         White
8  WILLIAMS   ROBIN      E             0          0             1              0             0          0       White  Haw/Pac Isl.
9  EASTWOOD   CLINT      E             0          0             0              0             0          1       White         White
Thomas Kimber
źródło
69
tylko uwaga: jeśli tylko dodajesz wiersz do swojej funkcji, możesz po prostu zrobić:df.apply(label_race, axis=1)
Paul H
1
Gdybym chciał zrobić coś podobnego z innym wierszem, czy mógłbym użyć tej samej funkcji? Na przykład z wyników, jeśli ['race_label'] == „White” zwraca „White” i tak dalej. Ale jeśli ['race_label'] == 'Nieznany' zwraca wartości z kolumny ['rno_defined']. Zakładam, że ta sama funkcja działałaby, ale nie wydaje mi się, aby wymyślić, jak uzyskać wartości z drugiej kolumny.
Dave
2
Możesz napisać nową funkcję, która patrzy na pole „race_label” i wysłać wyniki do nowego pola, lub - i myślę, że w tym przypadku może być lepiej, edytuj oryginalną funkcję, zmieniając ostatnią return 'Other'linię, na return row['rno_defined']którą powinna zastąp wartość z tej kolumny w tych przypadkach, w których zestaw instrukcji if / then nie znajduje dopasowania (tj. gdzie obecnie widzisz „Inne”).
Thomas Kimber
9
Możesz uprościć: df.apply(lambda row: label_race (row),axis=1)dodf.apply(label_race, axis=1)
user48956
5
W nowszych wersjach, jeśli otrzymujesz „SettingWithCopyWarning”, powinieneś spojrzeć na metodę „przypisania”. Zobacz: stackoverflow.com/a/12555510/3015186
np8
218

Ponieważ jest to pierwszy wynik Google dla „nowej kolumny pandy od innych”, oto prosty przykład:

import pandas as pd

# make a simple dataframe
df = pd.DataFrame({'a':[1,2], 'b':[3,4]})
df
#    a  b
# 0  1  3
# 1  2  4

# create an unattached column with an index
df.apply(lambda row: row.a + row.b, axis=1)
# 0    4
# 1    6

# do same but attach it to the dataframe
df['c'] = df.apply(lambda row: row.a + row.b, axis=1)
df
#    a  b  c
# 0  1  3  4
# 1  2  4  6

Jeśli go dostaniesz SettingWithCopyWarning, możesz to zrobić również w ten sposób:

fn = lambda row: row.a + row.b # define a function for the new column
col = df.apply(fn, axis=1) # get column data with an index
df = df.assign(c=col.values) # assign values to column 'c'

Źródło: https://stackoverflow.com/a/12555510/243392

A jeśli nazwa kolumny zawiera spacje, możesz użyć następującej składni:

df = df.assign(**{'some column name': col.values})

A oto dokumentacja do zastosowania i przypisania .

Brian Burns
źródło
1
Krótka odpowiedź, destylowana do sedna!
Frode Akselsen
1
Rozumiem, SettingWithCopyWarningkiedy to robię. df['c'] = df.apply(lambda row: row.a + row.b, axis=1) Czy to jest prawdziwy problem, czy nie powinienem się tym martwić?
Nate,
2
@Nate Nigdy nie dostałem tego ostrzeżenia - może to zależy od danych w ramce danych? Ale poprawiłem odpowiedź na podstawie innej odpowiedzi z 2017 r.
Brian Burns,
57

Powyższe odpowiedzi są całkowicie poprawne, ale istnieje wektoryzowane rozwiązanie w postaci numpy.select. Umożliwia to definiowanie warunków, a następnie definiowanie wyników dla tych warunków, znacznie wydajniej niż przy użyciu apply:


Najpierw zdefiniuj warunki:

conditions = [
    df['eri_hispanic'] == 1,
    df[['eri_afr_amer', 'eri_asian', 'eri_hawaiian', 'eri_nat_amer', 'eri_white']].sum(1).gt(1),
    df['eri_nat_amer'] == 1,
    df['eri_asian'] == 1,
    df['eri_afr_amer'] == 1,
    df['eri_hawaiian'] == 1,
    df['eri_white'] == 1,
]

Teraz zdefiniuj odpowiednie dane wyjściowe:

outputs = [
    'Hispanic', 'Two Or More', 'A/I AK Native', 'Asian', 'Black/AA', 'Haw/Pac Isl.', 'White'
]

Wreszcie, używając numpy.select:

res = np.select(conditions, outputs, 'Other')
pd.Series(res)

0           White
1        Hispanic
2           White
3           White
4           Other
5           White
6     Two Or More
7           White
8    Haw/Pac Isl.
9           White
dtype: object

Dlaczego warto z numpy.selectniego korzystać apply? Oto kilka kontroli wydajności:

df = pd.concat([df]*1000)

In [42]: %timeit df.apply(lambda row: label_race(row), axis=1)
1.07 s ± 4.16 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [44]: %%timeit
    ...: conditions = [
    ...:     df['eri_hispanic'] == 1,
    ...:     df[['eri_afr_amer', 'eri_asian', 'eri_hawaiian', 'eri_nat_amer', 'eri_white']].sum(1).gt(1),
    ...:     df['eri_nat_amer'] == 1,
    ...:     df['eri_asian'] == 1,
    ...:     df['eri_afr_amer'] == 1,
    ...:     df['eri_hawaiian'] == 1,
    ...:     df['eri_white'] == 1,
    ...: ]
    ...:
    ...: outputs = [
    ...:     'Hispanic', 'Two Or More', 'A/I AK Native', 'Asian', 'Black/AA', 'Haw/Pac Isl.', 'White'
    ...: ]
    ...:
    ...: np.select(conditions, outputs, 'Other')
    ...:
    ...:
3.09 ms ± 17 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Używanie numpy.selectzapewnia nam znacznie lepszą wydajność, a rozbieżność będzie się zwiększać w miarę wzrostu danych.

użytkownik3483203
źródło
8
To rozwiązanie jest tak niedoceniane. Wiedziałem, że mogę zrobić coś podobnego z aplikacją, ale szukałem alternatywy, ponieważ muszę wykonać tę operację dla tysięcy plików. Cieszę się, że znalazłem twój post.
mlx
Mam problem z utworzeniem czegoś podobnego. Pojawia się komunikat o błędzie „wartość prawdy w serii jest niejednoznaczna ...”. Mój kod to Kansas_City = ['ND', 'SD', 'NE', 'KS', 'MN', 'IA', 'MO'] warunki = [df_merge ['state_alpha'] w Kansas_City] wyjścia = [' Kansas City '] df_merge [' Region '] = np.select (warunki, wyniki,' Inne ') Czy ktoś może pomóc?
Shawn Schreier
3
To powinna być zaakceptowana odpowiedź. Inne są w porządku, ale gdy pracujesz na większych danych, ten jest jedynym, który działa i działa niesamowicie szybko.
TheProletariat
29

.apply()przyjmuje funkcję jako pierwszy parametr; przekaż label_racefunkcję w następujący sposób:

df['race_label'] = df.apply(label_race, axis=1)

Nie trzeba tworzyć funkcji lambda, aby przekazać funkcję.

Gabrielle Simard-Moore
źródło
12

Spróbuj tego,

df.loc[df['eri_white']==1,'race_label'] = 'White'
df.loc[df['eri_hawaiian']==1,'race_label'] = 'Haw/Pac Isl.'
df.loc[df['eri_afr_amer']==1,'race_label'] = 'Black/AA'
df.loc[df['eri_asian']==1,'race_label'] = 'Asian'
df.loc[df['eri_nat_amer']==1,'race_label'] = 'A/I AK Native'
df.loc[(df['eri_afr_amer'] + df['eri_asian'] + df['eri_hawaiian'] + df['eri_nat_amer'] + df['eri_white']) > 1,'race_label'] = 'Two Or More'
df.loc[df['eri_hispanic']==1,'race_label'] = 'Hispanic'
df['race_label'].fillna('Other', inplace=True)

O / P:

     lname   fname rno_cd  eri_afr_amer  eri_asian  eri_hawaiian  \
0      MOST    JEFF      E             0          0             0   
1    CRUISE     TOM      E             0          0             0   
2      DEPP  JOHNNY    NaN             0          0             0   
3     DICAP     LEO    NaN             0          0             0   
4    BRANDO  MARLON      E             0          0             0   
5     HANKS     TOM    NaN             0          0             0   
6    DENIRO  ROBERT      E             0          1             0   
7    PACINO      AL      E             0          0             0   
8  WILLIAMS   ROBIN      E             0          0             1   
9  EASTWOOD   CLINT      E             0          0             0   

   eri_hispanic  eri_nat_amer  eri_white rno_defined    race_label  
0             0             0          1       White         White  
1             1             0          0       White      Hispanic  
2             0             0          1     Unknown         White  
3             0             0          1     Unknown         White  
4             0             0          0       White         Other  
5             0             0          1     Unknown         White  
6             0             0          1       White   Two Or More  
7             0             0          1       White         White  
8             0             0          0       White  Haw/Pac Isl.  
9             0             0          1       White         White 

użyj .loczamiastapply .

poprawia wektoryzację.

.loc działa w prosty sposób, zamaskuj wiersze w oparciu o warunek, zastosuj wartości do zamrożonych wierszy.

więcej informacji na stronie, .loc docs

Wskaźniki wydajności:

Zaakceptowana odpowiedź:

def label_race (row):
   if row['eri_hispanic'] == 1 :
      return 'Hispanic'
   if row['eri_afr_amer'] + row['eri_asian'] + row['eri_hawaiian'] + row['eri_nat_amer'] + row['eri_white'] > 1 :
      return 'Two Or More'
   if row['eri_nat_amer'] == 1 :
      return 'A/I AK Native'
   if row['eri_asian'] == 1:
      return 'Asian'
   if row['eri_afr_amer']  == 1:
      return 'Black/AA'
   if row['eri_hawaiian'] == 1:
      return 'Haw/Pac Isl.'
   if row['eri_white'] == 1:
      return 'White'
   return 'Other'

df=pd.read_csv('dataser.csv')
df = pd.concat([df]*1000)

%timeit df.apply(lambda row: label_race(row), axis=1)

1,15 s ± 46,5 ms na pętlę (średnia ± odchylenie standardowe z 7 przebiegów, po 1 pętli)

Moja proponowana odpowiedź:

def label_race(df):
    df.loc[df['eri_white']==1,'race_label'] = 'White'
    df.loc[df['eri_hawaiian']==1,'race_label'] = 'Haw/Pac Isl.'
    df.loc[df['eri_afr_amer']==1,'race_label'] = 'Black/AA'
    df.loc[df['eri_asian']==1,'race_label'] = 'Asian'
    df.loc[df['eri_nat_amer']==1,'race_label'] = 'A/I AK Native'
    df.loc[(df['eri_afr_amer'] + df['eri_asian'] + df['eri_hawaiian'] + df['eri_nat_amer'] + df['eri_white']) > 1,'race_label'] = 'Two Or More'
    df.loc[df['eri_hispanic']==1,'race_label'] = 'Hispanic'
    df['race_label'].fillna('Other', inplace=True)
df=pd.read_csv('s22.csv')
df = pd.concat([df]*1000)

%timeit label_race(df)

24,7 ms ± 1,7 ms na pętlę (średnia ± odchylenie standardowe z 7 przebiegów, po 10 pętli)

Mohamed Thasin ah
źródło