Jak mogę użyć funkcji apply () dla pojedynczej kolumny?

Mam ramkę danych pandy z dwiema kolumnami. Muszę zmienić wartości pierwszej kolumny bez wpływu na drugą i odzyskać całą ramkę danych z tylko wartości pierwszej kolumny zmienione. Jak mogę to zrobić używając apply in pands?

Author: Seanny123, 2016-01-23

7 answers

Podano przykładową ramkę danych df jako:

a,b
1,2
2,3
3,4
4,5

To czego chcesz to:

df['a'] = df['a'].apply(lambda x: x + 1)

Zwraca:

   a  b
0  2  2
1  3  3
2  4  4
3  5  5
 426
Author: Fabio Lamanna,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-21 21:54:51

Dla pojedynczej kolumny lepiej użyć map(), TAK:

df = pd.DataFrame([{'a': 15, 'b': 15, 'c': 5}, {'a': 20, 'b': 10, 'c': 7}, {'a': 25, 'b': 30, 'c': 9}])

    a   b  c
0  15  15  5
1  20  10  7
2  25  30  9



df['a'] = df['a'].map(lambda a: a / 2.)

      a   b  c
0   7.5  15  5
1  10.0  10  7
2  12.5  30  9
 94
Author: George Petrov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-22 14:18:15

W ogóle nie potrzebujesz funkcji. Możesz pracować bezpośrednio na całej kolumnie.

Przykładowe dane:

>>> df = pd.DataFrame({'a': [100, 1000], 'b': [200, 2000], 'c': [300, 3000]})
>>> df

      a     b     c
0   100   200   300
1  1000  2000  3000

Połowa wszystkich wartości w kolumnie a:

>>> df.a = df.a / 2
>>> df

     a     b     c
0   50   200   300
1  500  2000  3000
 51
Author: Mike Müller,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-22 16:18:07

Chociaż podane odpowiedzi są poprawne, modyfikują początkową ramkę danych, co nie zawsze jest pożądane (a biorąc pod uwagę, że OP poprosił o przykłady " używając apply", może być tak, że chcieli wersji, która zwróci nową ramkę danych, jak robi to apply).

Jest to możliwe za pomocą assign: Ważne jest, aby assign do istniejących kolumn, jak stwierdza dokumentacja (nacisk jest mój):

Przypisz nowe kolumny do ramki danych.

Zwraca nowy obiekt z wszystkie oryginalne kolumny oprócz nowych. istniejące kolumny, które zostaną ponownie przypisane, zostaną nadpisane.

W skrócie:

In [1]: import pandas as pd

In [2]: df = pd.DataFrame([{'a': 15, 'b': 15, 'c': 5}, {'a': 20, 'b': 10, 'c': 7}, {'a': 25, 'b': 30, 'c': 9}])

In [3]: df.assign(a=lambda df: df.a / 2)
Out[3]: 
      a   b  c
0   7.5  15  5
1  10.0  10  7
2  12.5  30  9

In [4]: df
Out[4]: 
    a   b  c
0  15  15  5
1  20  10  7
2  25  30  9

Zauważ, że funkcja zostanie przekazana całej ramce danych, a nie tylko kolumnie, którą chcesz zmodyfikować, więc musisz upewnić się, że wybrałeś właściwą kolumnę w swoim lambda.

 27
Author: Thibaut Dubernet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-06-26 08:20:54

Biorąc pod uwagę następujący dataframe df i funkcję complex_function,

  import pandas as pd

  def complex_function(x, y=0):
      if x > 5 and x > y:
          return 1
      else:
          return 2

  df = pd.DataFrame(data={'col1': [1, 4, 6, 2, 7], 'col2': [6, 7, 1, 2, 8]})
     col1  col2
  0     1     6
  1     4     7
  2     6     1
  3     2     2
  4     7     8

Istnieje kilka rozwiązań, które można zastosować tylko w jednej kolumnie. Poniżej wyjaśnię je szczegółowo.

I. proste rozwiązanie

Proste rozwiązanie to to z @ Fabio Lamanna:

  df['col1'] = df['col1'].apply(complex_function)

Wyjście:

     col1  col2
  0     2     6
  1     2     7
  2     1     1
  3     2     2
  4     1     8

Modyfikowana jest tylko pierwsza kolumna, Druga kolumna pozostaje bez zmian. Rozwiązanie jest piękne. Jest to tylko jedna linijka kodu i czyta prawie podobnie jak w języku angielskim: "weź 'col1' i zastosuj do niego funkcję complex_function."

Jeśli jednak potrzebujesz danych z innej kolumny, np. 'col2', to nie działa. Jeśli chcesz przekazać wartości 'col2' do zmiennej y z complex_function, potrzebujesz czegoś innego.

II. rozwiązanie wykorzystujące cały dataframe

Alternatywnie, można użyć całego dataframe jak opisano w tym lub to tak post:

  df['col1'] = df.apply(lambda x: complex_function(x['col1']), axis=1)

Lub jeśli wolisz (jak ja) a rozwiązanie bez funkcji lambda:

  def apply_complex_function(x): return complex_function(x['col1'])
  df['col1'] = df.apply(apply_complex_function, axis=1) 

W tym rozwiązaniu dzieje się wiele rzeczy, które należy wyjaśnić. Funkcja apply () działa na pd.Seria i pd.Ramka danych. Ale nie możesz użyć df['col1'] = df.apply(complex_function).loc[:, 'col1'], ponieważ rzuciłoby ValueError.

Dlatego musisz podać informację, której kolumny użyć. Aby skomplikować sprawę, funkcja apply () akceptuje tylko wywołania . Aby to rozwiązać, musisz zdefiniować funkcję (lambda) z kolumną x['col1'] jako argumentem; tzn. zawijamy informacje o kolumnie w inną funkcję.

Niestety, domyślną wartością parametru axis jest zero (axis=0), co oznacza, że będzie próbował wykonywać polecenia w kolumnach, a nie w wierszach. To nie był problem w pierwszym rozwiązaniu, ponieważ daliśmy apply() a pd.Seria. Ale teraz wejście jest ramką danych i musimy być jawni (axis=1). (Dziwię się, jak często o tym zapominam.)

To, czy wolisz wersję z funkcją lambda, czy Bez, jest subiektywne. In my opinion linia kodu jest na tyle skomplikowana, że można ją odczytać nawet bez wrzuconej funkcji lambda. Potrzebujesz tylko funkcji (lambda) jako owijarki. To tylko kod kotła. Czytelnik nie powinien się tym przejmować.

Teraz możesz łatwo zmodyfikować To rozwiązanie, aby uwzględnić drugą kolumnę:

    def apply_complex_function(x): return complex_function(x['col1'], x['col2'])
    df['col1'] = df.apply(apply_complex_function, axis=1)

Wyjście:

     col1  col2
  0     2     6
  1     2     7
  2     1     1
  3     2     2
  4     2     8

W indeksie 4 wartość zmieniła się z 1 na 2, ponieważ pierwszy warunek 7 > 5 jest prawdziwy, ale drugi warunek 7 > 8 jest fałszywy.

Zauważ, że ty potrzebna jest tylko zmiana pierwszej linii kodu (tj. funkcji), a nie drugiej linii.


Side note

nigdy nie umieszczaj informacji o kolumnie w swojej funkcji.

  def bad_idea(x):
      return x['col1'] ** 2

Robiąc to, uzależniasz ogólną funkcję od nazwy kolumny! Jest to zły pomysł, ponieważ następnym razem, gdy chcesz użyć tej funkcji,nie możesz. Gorzej: być może zmieniasz nazwę kolumny w innej ramce danych tylko po to, aby działała z istniejącą funkcją. (Been there, done to. To jest śliski stok!)


III. alternatywne rozwiązania bez użycia apply ()

Chociaż OP wyraźnie poprosił o rozwiązanie za pomocą apply(), zaproponowano alternatywne rozwiązania. Na przykład, odpowiedź @ George Petrov zaproponował użycie map (), odpowiedź @ Thibaut Dubernet zaproponował assign ().

W pełni zgadzam się, że apply() jest rzadko najlepszym rozwiązaniem , ponieważ apply () jest Nie wektoryzowane. Jest to działanie elementarne z kosztowne wywołanie funkcji i napowietrzenie z pd.Seria.

Jednym z powodów użycia apply() jest to, że chcesz użyć istniejącej funkcji, a wydajność nie jest problemem. Albo twoja funkcja jest tak złożona, że nie ma wektoryzowanej wersji.

Innym powodem użycia apply() jest kombinacja z groupby () . zwróć uwagę, że ramka danych.apply () i GroupBy.apply () to różne funkcje.

Więc to ma sens, aby rozważyć niektóre alternatywy:

  • Działa tylko na pd.Serii, ale akceptuje dict i pd.Seria jako wejście. Używanie map() z funkcją jest prawie wymienne z używaniem apply(). Może być szybsza niż apply (). Zobacz to tak post aby uzyskać więcej szczegółów.
  df['col1'] = df['col1'].map(complex_function)
  • {[24] } jest prawie identyczny dla ramek danych. Nie obsługuje pd.Serii i zawsze zwróci ramkę danych. Jednak może być szybciej. dokumentacja stwierdza: "W obecnym implementacja applymap wywołuje func dwa razy w pierwszej kolumnie/wierszu, aby zdecydować, czy może przyjąć szybką lub powolną ścieżkę kodu.". Ale jeśli wydajność naprawdę się liczy, powinieneś poszukać alternatywnej drogi.
  df['col1'] = df.applymap(complex_function).loc[:, 'col1']
  • assign() nie jest wykonalnym zamiennikiem apply (). Ma podobne zachowanie tylko w najbardziej podstawowych przypadkach użycia. Nie działa z complex_function. Nadal potrzebujesz apply (), jak widać w poniższym przykładzie. głównym przypadkiem użycia assign () jest metoda chaining , ponieważ zwraca ramkę danych bez zmiany oryginalnej ramki danych.
  df['col1'] = df.assign(col1=df.col1.apply(complex_function))

Załącznik: jak przyspieszyć aplikowanie?

Wspominam o tym tylko tutaj, ponieważ sugerowały to inne odpowiedzi, np. @ durjoy. Lista nie jest wyczerpująca:

  1. Nie używaj apply ().To nie żart. Dla większości operacji numerycznych wektoryzowana metoda istnieje w pandach. Bloki If / else mogą być często refakturowane kombinacją logicznych indeksowanie i .loc. Mój przykład complex_function mógłby zostać zrefakturowany w ten sposób.
  2. Refaktor do Cythona. jeśli masz złożone równanie, a parametry równania są w Twojej ramce danych, może to być dobry pomysł. Sprawdź oficjalny podręcznik użytkownika pandas aby uzyskać więcej informacji.
  3. użyj parametru raw=True. teoretycznie powinno to poprawić wydajność apply() jeśli stosujesz tylko funkcję redukcji NumPy , ponieważ koszty policji.Seria jest usuwana. Oczywiście, twoja funkcja musi zaakceptować ndarray. Musisz zmienić swoją funkcję na NumPy. W ten sposób uzyskasz ogromny wzrost wydajności.
  4. użyj pakietów innych firm. pierwszą rzeczą, którą powinieneś spróbować jest Numba . Nie wiem swifter wspomniany przez @ durjoy; i prawdopodobnie wiele innych pakietów jest tu wartych wzmianki.
  5. Try / Fail / Repeat. jak wspomniano powyżej, map () i applymap() może być szybsza - w zależności od przypadku użycia. Wystarczy czas na różne wersje i wybrać najszybszy. Takie podejście jest najbardziej żmudne z najmniejszym wzrostem wydajności.
 21
Author: above_c_level,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-07-18 12:01:22

Jeśli jesteś naprawdę zaniepokojony szybkością wykonania funkcji apply i masz ogromny zbiór danych do pracy, możesz użyć swifter do szybszego wykonania, oto przykład swifter na ramce danych pandas:

import pandas as pd
import swifter

def fnc(m):
    return m*3+4

df = pd.DataFrame({"m": [1,2,3,4,5,6], "c": [1,1,1,1,1,1], "x":[5,3,6,2,6,1]})

# apply a self created function to a single column in pandas
df["y"] = df.m.swifter.apply(fnc)

To pozwoli wszystkim rdzeniom CPU obliczyć wynik, dzięki czemu będzie znacznie szybciej niż normalnie zastosuj funkcje. Daj mi znać, jeśli się przyda.

 17
Author: durjoy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-08-30 04:44:38

Pozwól mi spróbować skomplikowanych obliczeń przy użyciu datetime i biorąc pod uwagę null lub puste przestrzenie. Skracam 30 lat na kolumnie datetime i używam metody apply oraz lambda i konwertuję format datetime. Linia if x != '' else x odpowiednio zajmie się wszystkimi pustymi spacjami lub nullami.

df['Date'] = df['Date'].fillna('')
df['Date'] = df['Date'].apply(lambda x : ((datetime.datetime.strptime(str(x), '%m/%d/%Y') - datetime.timedelta(days=30*365)).strftime('%Y%m%d')) if x != '' else x)
 4
Author: Harry_pb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-02-14 15:12:37