Jak zastosować funkcję do dwóch kolumn ramki danych Pandas


368

Załóżmy, że mam dfco ma kolumny 'ID', 'col_1', 'col_2'. I definiuję funkcję:

f = lambda x, y : my_function_expression.

Teraz chcę zastosować fdo df„s dwie kolumny 'col_1', 'col_2'do elementu mądry obliczy nową kolumnę 'col_3', trochę jak:

df['col_3'] = df[['col_1','col_2']].apply(f)  
# Pandas gives : TypeError: ('<lambda>() takes exactly 2 arguments (1 given)'

Jak zrobić ?

** Dodaj próbkę szczegółową jak poniżej ***

import pandas as pd

df = pd.DataFrame({'ID':['1','2','3'], 'col_1': [0,2,3], 'col_2':[1,4,5]})
mylist = ['a','b','c','d','e','f']

def get_sublist(sta,end):
    return mylist[sta:end+1]

#df['col_3'] = df[['col_1','col_2']].apply(get_sublist,axis=1)
# expect above to output df as below 

  ID  col_1  col_2            col_3
0  1      0      1       ['a', 'b']
1  2      2      4  ['c', 'd', 'e']
2  3      3      5  ['d', 'e', 'f']

4
czy możesz zastosować f bezpośrednio do kolumn: df ['col_3'] = f (df ['col_1'], df ['col_2'])
— btel

1
przydałoby się wiedzieć, co fsię dzieje
— tehmisvh,

2
nie, df ['col_3'] = f (df ['col_1'], df ['col_2']) nie działa. Dla f akceptuje tylko dane skalarne, a nie wektorowe. OK, możesz założyć f = lambda x, y: x + y. (oczywiście, mój prawdziwy f nie jest taki prosty, w przeciwnym razie mogę bezpośrednio df ['col_3'] = df ['col_1'] + df ['col_2'])
— bigbug

1
Znalazłem powiązane pytania i odpowiedzi pod poniższym adresem URL, ale moim problemem jest obliczenie nowej kolumny na podstawie dwóch istniejących kolumn, a nie 2 z 1. stackoverflow.com/questions/12356501/…
— bigbug

Myślę, że moja odpowiedź stackoverflow.com/a/52854800/5447172 odpowiada na to w najbardziej Pythoński / Pandaniczny sposób, bez obejść ani indeksowania numerycznego. Wytwarza dokładnie dane wyjściowe wymagane w twoim przykładzie.
— ajrwhite

Odpowiedzi:


291

Oto przykład użycia applyw ramce danych, z którą dzwonię axis = 1.

Zauważ, że różnica polega na tym, że zamiast próbować przekazać dwie wartości do funkcji f, przepisz funkcję, aby zaakceptować obiekt serii pandy, a następnie indeksuj serię, aby uzyskać potrzebne wartości.

In [49]: df
Out[49]: 
          0         1
0  1.000000  0.000000
1 -0.494375  0.570994
2  1.000000  0.000000
3  1.876360 -0.229738
4  1.000000  0.000000

In [50]: def f(x):    
   ....:  return x[0] + x[1]  
   ....:  

In [51]: df.apply(f, axis=1) #passes a Series object, row-wise
Out[51]: 
0    1.000000
1    0.076619
2    1.000000
3    1.646622
4    1.000000

W zależności od przypadku użycia czasem pomocne jest utworzenie groupobiektu pandy , a następnie użycie applygo w grupie.


Tak, próbowałem użyć Apply, ale nie mogę znaleźć prawidłowego wyrażenia składniowego. A jeśli każdy wiersz df jest unikalny, nadal używasz groupby?
— bigbug

Dodałem przykład do mojej odpowiedzi, mam nadzieję, że zrobi to, czego szukasz. Jeśli nie, podaj bardziej szczegółową przykładową funkcję, ponieważ sumzostała pomyślnie rozwiązana za pomocą dowolnej z sugerowanych dotychczas metod.
— Aman

1
Czy mógłbyś wkleić swój kod? Przepisuję funkcję: def get_sublist (x): return mylist [x [1]: x [2] + 1] i df ['col_3'] = df.apply (get_sublist, axis = 1) daje 'ValueError: operandy mogą nie będą nadawane razem z kształtami (2) (3) '
— bigbug

3
@Aman: w Pandach w wersji 0.14.1 (i prawdopodobnie wcześniejszych), use może również używać wyrażenia lambda. Podaj dfzdefiniowany obiekt, inne podejście (z równoważnymi wynikami) to df.apply(lambda x: x[0] + x[1], axis = 1).
— Jubbles,

2
@CanCeylan możesz po prostu użyć nazw kolumn w funkcji zamiast indeksów, wtedy nie musisz się martwić o zmianę kolejności lub uzyskać indeks według nazwy, np. Patrz stackoverflow.com/questions/13021654/…
— Davos

165

W Pandach istnieje czysty, jednowierszowy sposób:

df['col_3'] = df.apply(lambda x: f(x.col_1, x.col_2), axis=1)

Pozwala fto być funkcją zdefiniowaną przez użytkownika z wieloma wartościami wejściowymi i używa (bezpiecznych) nazw kolumn zamiast (niebezpiecznych) indeksów numerycznych w celu uzyskania dostępu do kolumn.

Przykład z danymi (na podstawie oryginalnego pytania):

import pandas as pd

df = pd.DataFrame({'ID':['1', '2', '3'], 'col_1': [0, 2, 3], 'col_2':[1, 4, 5]})
mylist = ['a', 'b', 'c', 'd', 'e', 'f']

def get_sublist(sta,end):
    return mylist[sta:end+1]

df['col_3'] = df.apply(lambda x: get_sublist(x.col_1, x.col_2), axis=1)

Wyjście print(df):

  ID  col_1  col_2      col_3
0  1      0      1     [a, b]
1  2      2      4  [c, d, e]
2  3      3      5  [d, e, f]

Jeśli nazwy kolumn zawierają spacje lub mają wspólną nazwę z istniejącym atrybutem ramki danych, możesz indeksować za pomocą nawiasów kwadratowych:

df['col_3'] = df.apply(lambda x: f(x['col 1'], x['col 2']), axis=1)

2
Uwaga: jeśli używasz, axis=1a twoja kolumna jest nazywana name, tak naprawdę nie zwróci danych kolumny, ale index. Podobne do uzyskiwania namew groupby(). Rozwiązałem to, zmieniając nazwę mojej kolumny.
— Tom Hemmes

2
TO JEST TO! Po prostu nie zdawałem sobie sprawy, że możesz wstawić funkcje lambda z wieloma parametrami wejściowymi do lambdas. Należy zauważyć (myślę), że używasz DF.apply () zamiast Series.apply (). Pozwala to na indeksowanie pliku df przy użyciu dwóch kolumn, które chcesz, i przekazanie całej kolumny do funkcji, ale ponieważ używasz funkcji Apply (), stosuje funkcję w sposób elementarny wzdłuż całej kolumny. Znakomity! Dziękujemy za wysłanie wiadomości!
— Data-phile

1
WRESZCIE! Uratowałeś mi dzień!
— Mysterio

Myślę, że sugerowanym sposobem jest zrobienie tego df.loc [:, 'new col'] = df.apply .....
— valearner

@valearner Nie wydaje mi się, żeby .locw tym przykładzie istniał jakiś powód . Może to być potrzebne, jeśli dostosujesz to do innego ustawienia problemu (np. Praca z plasterkami).
— ajrwhite

86

Prostym rozwiązaniem jest:

df['col_3'] = df[['col_1','col_2']].apply(lambda x: f(*x), axis=1)

1
czym różni się ta odpowiedź od podejścia w pytaniu: df ['col_3'] = df [['' col_1 ',' col_2 ']]. zastosuj (f) tylko w celu potwierdzenia, podejście w pytaniu nie zadziałało, ponieważ plakat nie określił tej osi = 1, domyślnie oś = 0?
— Lost1

1
Ta odpowiedź jest porównywalna z odpowiedzią @ Anman, ale nieco bardziej płynna. Konstruuje anonimową funkcję, która przyjmuje iterowalną i rozpakowuje ją przed przekazaniem jej do funkcji f.
— tiao,

39

Ciekawe pytanie! moja odpowiedź jak poniżej:

import pandas as pd

def sublst(row):
    return lst[row['J1']:row['J2']]

df = pd.DataFrame({'ID':['1','2','3'], 'J1': [0,2,3], 'J2':[1,4,5]})
print df
lst = ['a','b','c','d','e','f']

df['J3'] = df.apply(sublst,axis=1)
print df

Wynik:

  ID  J1  J2
0  1   0   1
1  2   2   4
2  3   3   5
  ID  J1  J2      J3
0  1   0   1     [a]
1  2   2   4  [c, d]
2  3   3   5  [d, e]

Zmieniłem nazwę kolumny na ID, J1, J2, J3, aby zapewnić ID <J1 <J2 <J3, więc kolumna wyświetla się we właściwej kolejności.

Jeszcze jedna krótka wersja:

import pandas as pd

df = pd.DataFrame({'ID':['1','2','3'], 'J1': [0,2,3], 'J2':[1,4,5]})
print df
lst = ['a','b','c','d','e','f']

df['J3'] = df.apply(lambda row:lst[row['J1']:row['J2']],axis=1)
print df

23

Metoda, której szukasz, to Series.combine. Wydaje się jednak, że należy zachować ostrożność przy typach danych. W twoim przykładzie zadzwoniłbyś (tak jak ja podczas testowania odpowiedzi) naiwnie

df['col_3'] = df.col_1.combine(df.col_2, func=get_sublist)

Powoduje to jednak błąd:

ValueError: setting an array element with a sequence.

Domyślam się, że oczekuje, że wynik będzie tego samego typu, co seria wywołująca metodę (tutaj df.col_1). Jednak następujące prace:

df['col_3'] = df.col_1.astype(object).combine(df.col_2, func=get_sublist)

df

   ID   col_1   col_2   col_3
0   1   0   1   [a, b]
1   2   2   4   [c, d, e]
2   3   3   5   [d, e, f]

12

Sposób, w jaki napisałeś, wymaga dwóch danych wejściowych. Jeśli spojrzysz na komunikat o błędzie, to znaczy, że nie podajesz dwóch danych wejściowych dla f, tylko jedno. Komunikat o błędzie jest poprawny.
Niedopasowanie jest spowodowane tym, że df [['col1', 'col2']] zwraca pojedynczą ramkę danych z dwiema kolumnami, a nie dwiema osobnymi kolumnami.

Musisz zmienić swój f, aby wymagał pojedynczego wejścia, zachowaj powyższą ramkę danych jako wejście, a następnie podziel ją na x, y wewnątrz ciała funkcji. Następnie zrób wszystko, czego potrzebujesz i zwróć jedną wartość.

Potrzebujesz tej sygnatury funkcji, ponieważ składnia to .apply (f) Więc f musi wziąć jedną rzecz = ramkę danych, a nie dwie rzeczy, czego oczekuje twój obecny f.

Ponieważ nie podałeś jeszcze części f, nie mogę pomóc w szczegółach - ale powinno to dać wyjście bez fundamentalnej zmiany kodu lub użycia innych metod zamiast zastosowania


12

Zagłosuję na np. Wektor. Pozwala strzelać ponad x liczby kolumn i nie zajmować się ramką danych w funkcji, więc świetnie nadaje się do funkcji, których nie kontrolujesz lub nie robisz czegoś takiego jak wysyłanie 2 kolumn i stałej do funkcji (np. Col_1, col_2, 'bla').

import numpy as np
import pandas as pd

df = pd.DataFrame({'ID':['1','2','3'], 'col_1': [0,2,3], 'col_2':[1,4,5]})
mylist = ['a','b','c','d','e','f']

def get_sublist(sta,end):
    return mylist[sta:end+1]

#df['col_3'] = df[['col_1','col_2']].apply(get_sublist,axis=1)
# expect above to output df as below 

df.loc[:,'col_3'] = np.vectorize(get_sublist, otypes=["O"]) (df['col_1'], df['col_2'])


df

ID  col_1   col_2   col_3
0   1   0   1   [a, b]
1   2   2   4   [c, d, e]
2   3   3   5   [d, e, f]

1
To tak naprawdę nie odpowiada na pytanie za pomocą pand.
— mnky9800n

18
Pytanie brzmi: „Jak zastosować funkcję do dwóch kolumn ramki danych Pandas”, a nie „Jak zastosować funkcję do dwóch kolumn ramki danych Pandas tylko przy użyciu metod Pandas”, a numpy jest zależnością Pandas, więc trzeba ją mimo wszystko zainstalować, więc wydaje się to dziwnym zastrzeżeniem.
— Trae Wallace

12

Zwracanie listy applyjest niebezpieczną operacją, ponieważ nie można zagwarantować, że wynikowy obiekt będzie Serią lub ramką danych. W niektórych przypadkach mogą pojawić się wyjątki. Przejdźmy przez prosty przykład:

df = pd.DataFrame(data=np.random.randint(0, 5, (5,3)),
                  columns=['a', 'b', 'c'])
df
   a  b  c
0  4  0  0
1  2  0  1
2  2  2  2
3  1  2  2
4  3  0  0

Istnieją trzy możliwe wyniki przy zwrocie listy apply

1) Jeśli długość zwracanej listy nie jest równa liczbie kolumn, zwracana jest seria list.

df.apply(lambda x: list(range(2)), axis=1)  # returns a Series
0    [0, 1]
1    [0, 1]
2    [0, 1]
3    [0, 1]
4    [0, 1]
dtype: object

2) Gdy długość zwracanej listy jest równa liczbie kolumn, wówczas zwracana jest ramka danych i każda kolumna otrzymuje odpowiednią wartość z listy.

df.apply(lambda x: list(range(3)), axis=1) # returns a DataFrame
   a  b  c
0  0  1  2
1  0  1  2
2  0  1  2
3  0  1  2
4  0  1  2

3) Jeśli długość zwracanej listy jest równa liczbie kolumn dla pierwszego wiersza, ale ma co najmniej jeden wiersz, w którym lista ma inną liczbę elementów niż liczba kolumn, wywoływany jest błąd ValueError.

i = 0
def f(x):
    global i
    if i == 0:
        i += 1
        return list(range(3))
    return list(range(4))

df.apply(f, axis=1) 
ValueError: Shape of passed values is (5, 4), indices imply (5, 3)

Rozwiązanie problemu bez zastosowania

Korzystanie applyz osi = 1 jest bardzo wolne. Możliwe jest uzyskanie znacznie lepszej wydajności (szczególnie w przypadku większych zestawów danych) za pomocą podstawowych metod iteracyjnych.

Utwórz większą ramkę danych

df1 = df.sample(100000, replace=True).reset_index(drop=True)

Czasy

# apply is slow with axis=1
%timeit df1.apply(lambda x: mylist[x['col_1']: x['col_2']+1], axis=1)
2.59 s ± 76.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

# zip - similar to @Thomas
%timeit [mylist[v1:v2+1] for v1, v2 in zip(df1.col_1, df1.col_2)]  
29.5 ms ± 534 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

@Thomas answer

%timeit list(map(get_sublist, df1['col_1'],df1['col_2']))
34 ms ± 459 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

1
Miło jest zobaczyć tak szczegółowe odpowiedzi, z których można się nauczyć.
— Andrea Moro

7

Jestem pewien, że nie jest to tak szybkie, jak rozwiązania wykorzystujące operacje Pandas lub Numpy, ale jeśli nie chcesz przepisać swojej funkcji, możesz użyć mapy. Wykorzystując oryginalne przykładowe dane -

import pandas as pd

df = pd.DataFrame({'ID':['1','2','3'], 'col_1': [0,2,3], 'col_2':[1,4,5]})
mylist = ['a','b','c','d','e','f']

def get_sublist(sta,end):
    return mylist[sta:end+1]

df['col_3'] = list(map(get_sublist,df['col_1'],df['col_2']))
#In Python 2 don't convert above to list

W ten sposób moglibyśmy przekazać dowolną liczbę argumentów do funkcji. Wynik jest tym, czego chcieliśmy

ID  col_1  col_2      col_3
0  1      0      1     [a, b]
1  2      2      4  [c, d, e]
2  3      3      5  [d, e, f]

1
Jest to w rzeczywistości znacznie szybsze odpowiedzi, które używają applyzaxis=1
— Ted Petrou

2

Mój przykład na twoje pytania:

def get_sublist(row, col1, col2):
    return mylist[row[col1]:row[col2]+1]
df.apply(get_sublist, axis=1, col1='col_1', col2='col_2')

2

Jeśli masz duży zestaw danych, możesz użyć łatwego, ale szybszego (czasu wykonania) sposobu, aby to zrobić za pomocą przełącznika szybszego:

import pandas as pd
import swifter

def fnc(m,x,c):
    return m*x+c

df = pd.DataFrame({"m": [1,2,3,4,5,6], "c": [1,1,1,1,1,1], "x":[5,3,6,2,6,1]})
df["y"] = df.swifter.apply(lambda x: fnc(x.m, x.x, x.c), axis=1)

1

Przypuszczam, że nie chcesz zmieniać get_sublistfunkcji, a po prostu chcesz użyć applymetody DataFrame do wykonania zadania. Aby uzyskać pożądany rezultat, napisałem dwie funkcje pomocy: get_sublist_listi unlist. Jak sugeruje nazwa funkcji, najpierw uzyskaj listę podlisty, a następnie wypakuj podlistę z tej listy. Na koniec musimy wywołać applyfunkcję, aby następnie zastosować te dwie funkcje do df[['col_1','col_2']]DataFrame.

import pandas as pd

df = pd.DataFrame({'ID':['1','2','3'], 'col_1': [0,2,3], 'col_2':[1,4,5]})
mylist = ['a','b','c','d','e','f']

def get_sublist(sta,end):
    return mylist[sta:end+1]

def get_sublist_list(cols):
    return [get_sublist(cols[0],cols[1])]

def unlist(list_of_lists):
    return list_of_lists[0]

df['col_3'] = df[['col_1','col_2']].apply(get_sublist_list,axis=1).apply(unlist)

df

Jeśli nie użyjesz []do załączenia get_sublistfunkcji, get_sublist_listfunkcja zwróci prostą listę, podniesie się ValueError: could not broadcast input array from shape (3) into shape (2), jak wspomniał @Ted Petrou.

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.