Podsumowanie kursu Kaggle „Pandas” (1) — lekcje 1–3
Podsumowanie wykorzystania biblioteki Pandas do czyszczenia i przetwarzania danych. Streszczenie kursu Kaggle „Pandas” z drobnymi uzupełnieniami; część 1 obejmuje lekcje 1–3.
Poniżej porządkuję notatki z nauki na kursie Kaggle Pandas.
Ponieważ materiału jest całkiem sporo, podzieliłem go na 2 części.
- Część 1: lekcje 1–3 (ten wpis)
- Część 2: lekcje 4–6
Lekcja 1. Tworzenie, odczyt i zapis
Import Pandas
1
import pandas as pd
W Pandas są dwa kluczowe obiekty: DataFrame i Series.
DataFrame
DataFrame można traktować jak tabelę albo macierz. Składa się z macierzy niezależnych wpisów (entries); każdy wpis ma pewną wartość (value) i odpowiada jednemu wierszowi (row) lub rekordowi (record) oraz jednej kolumnie (column).
1
pd.DataFrame({'Yes': [50, 21], 'No': [131, 2]})
| Yes | No | |
|---|---|---|
| 0 | 50 | 131 |
| 1 | 21 | 2 |
Wpisy w DataFrame nie muszą być liczbami; poniżej przykład DataFrame z wartościami tekstowymi (recenzje pozostawione przez użytkowników).
1
pd.DataFrame({'Bob': ['I liked it.', 'It was awful.'], 'Sue': ['Pretty good.', 'Bland.']})
| Bob | Sue | |
|---|---|---|
| 0 | I liked it. | Pretty good. |
| 1 | It was awful. | Bland. |
Aby utworzyć obiekt DataFrame, używa się konstruktora pd.DataFrame() i składni słownika (dictionary) Pythona. Klucze (key) to nazwy kolumn, a wartości (value) to listy (list) elementów do wpisania. Jest to standardowy sposób deklarowania nowego DataFrame.
Przy tworzeniu DataFrame etykiety kolumn nadaje się jako nazwy kolumn, natomiast jeśli nie podasz osobno etykiet wierszy, Pandas przypisze liczby całkowite 0, 1, 2, …. W razie potrzeby etykiety wierszy można ustawić ręcznie. Lista etykiet wierszy w DataFrame nazywa się indeksem (Index) i można ją podać parametrem index w konstruktorze.
1
2
3
pd.DataFrame({'Bob': ['I liked it.', 'It was awful.'],
'Sue': ['Pretty good.', 'Bland.']},
index=['Product A', 'Product B'])
| Bob | Sue | |
|---|---|---|
| Product A | I liked it. | Pretty good. |
| Product B | It was awful. | Bland. |
Series
Series to ciąg (sequence) wartości danych, czyli inaczej wektor.
1
pd.Series([1, 2, 3, 4, 5])
Series jest w istocie pojedynczą kolumną DataFrame. Dlatego tak samo można określić indeks, a zamiast „nazwy kolumny” ma po prostu „nazwę” (name).
1
pd.Series([30, 35, 40], index=['12015 Sales', '12016 Sales', '12017 Sales'], name='Product A')
1
2
3
4
12015 Sales 30
12016 Sales 35
12017 Sales 40
Name: Product A, dtype: int64
Series i DataFrame są ze sobą ściśle powiązane. Pomaga myśleć o DataFrame jak o „wiązce” (zbiorze) obiektów Series.
Wczytywanie plików z danymi
W wielu przypadkach zamiast pisać dane ręcznie, wczytuje się istniejące dane. Dane mogą być zapisane w różnych formatach, ale najbardziej podstawowy to CSV. Zawartość pliku CSV zwykle wygląda tak:
Product A,Product B,Product C,
30,21,9,
35,34,1,
41,11,11
Czyli CSV to tabela, w której wartości są rozdzielane przecinkami (comma). Stąd nazwa „Comma-Separated Values”, CSV.
Aby wczytać dane w formacie CSV do DataFrame, używa się funkcji pd.read_csv().
1
product_reviews = pd.read_csv("../input/product-reviews/example-data.csv")
Atrybut shape pozwala sprawdzić wymiary DataFrame.
1
product_reviews.shape
1
(129971, 14)
Powyższy wynik oznacza, że DataFrame ma 129971 rekordów i 14 kolumn.
Metoda head() pozwala podejrzeć pierwsze pięć wierszy DataFrame.
1
product_reviews.head()
Funkcja pd.read_csv() ma ponad 30 parametrów. Na przykład, jeśli wczytywany plik CSV sam zawiera indeks, można ustawić parametr index_col, aby zamiast automatycznego indeksowania Pandas użył wskazanej kolumny jako indeksu.
1
product_reviews = pd.read_csv("../input/product-reviews/example-data.csv", index_col=0)
Zapisywanie plików z danymi
Metoda to_csv() pozwala wyeksportować DataFrame do pliku CSV. Użycie wygląda następująco:
1
product_reviews.to_csv("../output/product-reviews/example-data.csv")
Lekcja 2. Indeksowanie, wybieranie i przypisywanie
Wybieranie konkretnych wartości z DataFrame lub Series to etap, przez który przechodzi praktycznie każde przetwarzanie danych. Dlatego w pierwszej kolejności warto nauczyć się szybko i efektywnie wybierać potrzebne punkty danych.
Dostępory (accessors) wbudowane w Pythona
Natywne obiekty Pythona oferują świetne sposoby indeksowania danych, a Pandas zapewnia te same mechanizmy.
Atrybuty obiektu
W Pythonie do wartości atrybutu (property) obiektu można dostać się przez jego nazwę (attribute). Na przykład jeśli obiekt example_obj ma atrybut title, to można go odczytać jako example_obj.title. Analogicznie można odwoływać się do kolumn DataFrame.
1
reviews.country
1
2
3
4
5
6
0 Italy
1 Portugal
...
129969 France
129970 France
Name: country, Length: 129971, dtype: object
Indeksowanie jak w słowniku
W przypadku słownika w Pythonie do wartości można uzyskać dostęp operatorem indeksowania ([]). Tę samą metodę można stosować również do kolumn DataFrame.
1
reviews['country']
1
2
3
4
5
6
0 Italy
1 Portugal
...
129969 France
129970 France
Name: country, Length: 129971, dtype: object
Obie metody (dostęp przez atrybut i indeksowanie jak słownik) są poprawne, ale indeksowanie słownikowe ma tę zaletę, że radzi sobie z nazwami kolumn zawierającymi znaki zastrzeżone, np. spacje (np. reviews['country providence'] jest możliwe, natomiast reviews.country providence — nie).
Tak wybraną Series można dalej indeksować operatorem [], aby odczytać pojedynczą wartość.
1
reviews['country'][0]
1
'Italy'
Dostępory specyficzne dla Pandas
Podejścia opisane wyżej świetnie „współgrają” z resztą ekosystemu Pythona, ale Pandas udostępnia też własne, charakterystyczne dostępory: loc i iloc.
Wybór oparty o indeks (pozycję)
Korzystając z iloc, można wykonywać wybór oparty o indeks/pozycję (index-based selection). Wybiera się dane, wskazując pozycje jako liczby całkowite.
Na przykład w ten sposób wybierzesz pierwszy wiersz DataFrame:
1
reviews.iloc[0]
1
2
3
4
5
6
country Italy
description Aromas include tropical fruit, broom, brimston...
...
variety White Blend
winery Nicosia
Name: 0, Length: 13, dtype: object
W odróżnieniu od natywnego podejścia Pythona, gdzie najczęściej wybiera się najpierw kolumnę, a potem wiersz, iloc wybiera najpierw wiersze, a dopiero potem kolumny. Pierwszą kolumnę DataFrame można wybrać tak:
1
reviews.iloc[:, 0]
1
2
3
4
5
6
0 Italy
1 Portugal
...
129969 France
129970 France
Name: country, Length: 129971, dtype: object
W powyższym przykładzie użyto operatora :, aby wybrać wszystkie wiersze, a następnie w ich obrębie pierwszą kolumnę. Jeśli chcesz wybrać 2. (1) i 3. (2) wiersz pierwszej kolumny, zrób to tak:
1
reviews.iloc[1:3, 0]
1
2
3
1 Portugal
2 US
Name: country, dtype: object
Możesz też przekazać listę:
1
reviews.iloc[[1, 2], 0]
1
2
3
1 Portugal
2 US
Name: country, dtype: object
Można używać liczb ujemnych, aby wybierać dane „od końca”. Poniższy przykład wybiera ostatnie 5 wierszy:
1
reviews.iloc[-5:]
Wybór oparty o etykiety
Inną metodą jest użycie loc do wykonania wyboru opartego o etykiety (label-based selection). W tym przypadku wybiera się dane nie po położeniu, tylko po wartościach indeksu.
Na przykład, aby uzyskać wpis z kolumny country dla wiersza o indeksie 0:
1
reviews.loc[0, 'country']
1
'Italy'
iloc ignoruje wartości indeksu zbioru danych i traktuje całość jak jedną dużą macierz — dostęp do pojedynczych wpisów zależy wyłącznie od pozycji. Z kolei loc wykorzystuje informacje o indeksie. Ponieważ indeks często niesie istotną informację, w wielu sytuacjach loc jest bardziej intuicyjny niż iloc.
Różnica w sposobie wyznaczania zakresów w iloc i loc
iloc używa standardowego schematu indeksowania z biblioteki standardowej Pythona, więc 0:10 oznacza przedział domknięto-otwarty: od 0 do 10 bez 10, czyli 0,...,9.
Natomiast loc traktuje zakres jako domknięty, więc 0:10 oznacza od 0 do 10 włącznie, czyli 0,...,10.
Powód jest taki, że loc pozwala używać jako indeksu nie tylko liczb całkowitych, ale też dowolnych standardowych typów danych. Załóżmy np., że masz DataFrame z indeksami w rodzaju Apples, ..., Potatoes, ... i chcesz wybrać uprawy od „Apples” do „Potatoes” włącznie w porządku alfabetycznym. Ponieważ po „Potatoes” może pojawić się ciąg znaków „Potatoet”, zapis „od ‘Apples’ do przed ‘Potatoet’” (df.loc['Apples':'Potatoet']) jest mniej intuicyjny niż po prostu „od ‘Apples’ do ‘Potatoes’” (df.loc['Apples':'Potatoes']). Dla indeksów niebędących liczbami całkowitymi zwykle bardziej intuicyjny jest ten drugi zapis — dlatego loc stosuje domknięty zakres.
Poza tym reszta zachowania jest zasadniczo taka sama.
Osobiście, jeśli muszę wyznaczać zakres operatorem
:w zbiorze danych z rosnącym, całkowitoliczbowym indeksem, wolęiloc, żeby uniknąć zamieszania wynikającego z różnic w domknięciu zakresu. W pozostałych przypadkach częściej wybieram bardziej intuicyjneloc.
Modyfikowanie indeksu
Indeks można również dostosowywać. Metoda set_index() pozwala — jak w przykładzie poniżej — ustawić wybraną kolumnę jako nowy indeks.
1
reviews.set_index("title")
Wybór warunkowy
Powyższe podejścia dotyczyły wybierania i przetwarzania danych na podstawie strukturalnych własności DataFrame. Można jednak iść dalej i wybierać dane spełniające bardziej złożone warunki.
Załóżmy na przykład, że w DataFrame z informacjami o winach chcesz wybrać tylko wina z Włoch, które mają ocenę co najmniej 90.
1
reviews.country == 'Italy'
To wyrażenie zwraca Series z wartościami boolowskimi True/False.
1
2
3
4
5
6
0 True
1 False
...
129969 False
129970 False
Name: country, Length: 129971, dtype: bool
loc jest domyślnie oparty o etykiety, ale może też przyjmować tablice boolowskie albo sortowalne Series boolowskie. Dlatego można w ten sposób wybrać tylko wina z Włoch:
1
reviews.loc[reviews.country == 'Italy']
Wiele warunków można łączyć operatorami & albo |. Aby wybrać wina, które są z Włoch i jednocześnie mają co najmniej 90 punktów:
1
reviews.loc[(reviews.country == 'Italy') & (reviews.points >= 90)]
Aby wybrać wina, które są z Włoch lub mają co najmniej 90 punktów:
1
reviews.loc[(reviews.country == 'Italy') | (reviews.points >= 90)]
Pandas ma też kilka wbudowanych selektorów warunkowych, w szczególności isin oraz isnull/notnull.
isin zwraca maskę boolowską (True albo False) informującą, czy dana wartość należy do (is in) podanej listy. Można jej użyć do wyboru danych. Na przykład, aby wybrać wina z Włoch lub z Francji:
1
reviews.loc[reviews.country.isin(['Italy', 'France'])]
isna/notna służą do wybierania danych z brakami (NaN) lub bez braków. Na przykład, aby wybrać tylko wina, dla których cena nie jest brakująca:
1
reviews.loc[reviews.price.notna()]
Dla porządku: to nie było w oryginalnym kursie Kaggle, ale
ilocrównież może przyjmować tablice boolowskie (array). Jednak w przeciwieństwie dolocwspiera tylko tablice, a nie Series, więc trudniej to wykorzystać w podobny sposób jak powyżej.
Przypisywanie danych
Do DataFrame można też przypisywać nowe dane albo nadpisywać istniejące.
1
2
reviews['critic'] = 'everyone'
reviews['critic']
1
2
3
4
5
6
0 everyone
1 everyone
...
129969 everyone
129970 everyone
Name: critic, Length: 129971, dtype: object
1
2
reviews['index_backwards'] = range(len(reviews), 0, -1)
reviews['index_backwards']
1
2
3
4
5
6
0 129971
1 129970
...
129969 2
129970 1
Name: index_backwards, Length: 129971, dtype: int64
Lekcja 3. Funkcje podsumowujące i mapowania
Podgląd ogólnej charakterystyki danych
Metoda describe() daje wysokopoziomowe podsumowanie danej kolumny.
1
reviews.points.describe()
1
2
3
4
5
6
count 129971.000000
mean 88.447138
...
75% 91.000000
max 100.000000
Name: points, Length: 8, dtype: float64
Wyjście describe() zależy od typu danych. Dla danych tekstowych (string) wynik wygląda np. tak:
1
reviews.taster_name.describe()
1
2
3
4
5
count 103727
unique 19
top Roger Voss
freq 25514
Name: taster_name, dtype: object
Można też wyciągać wybrane statystyki.
1
reviews.points.mean()
1
88.44713820775404
1
reviews.taster_name.unique()
1
2
3
4
5
6
7
array(['Kerin O’Keefe', 'Roger Voss', 'Paul Gregutt',
'Alexander Peartree', 'Michael Schachner', 'Anna Lee C. Iijima',
'Virginie Boone', 'Matt Kettmann', nan, 'Sean P. Sullivan',
'Jim Gordon', 'Joe Czerwinski', 'Anne Krebiehl\xa0MW',
'Lauren Buzzeo', 'Mike DeSimone', 'Jeff Jenssen',
'Susan Kostrzewa', 'Carrie Dykes', 'Fiona Adams',
'Christina Pickard'], dtype=object)
Jeśli chcesz wiedzieć, ile razy występuje każda unikalna wartość w DataFrame, użyj metody value_counts().
1
reviews.taster_name.value_counts()
1
2
3
4
5
6
Roger Voss 25514
Michael Schachner 15134
...
Fiona Adams 27
Christina Pickard 6
Name: taster_name, Length: 19, dtype: int64
Odwzorowania (Maps)
Odwzorowanie (map) to termin zapożyczony z matematyki i oznacza funkcję, która przyporządkowuje elementy jednego zbioru elementom innego zbioru. W data science często zachodzi potrzeba przekształcania danych do innej postaci — i wtedy używa się odwzorowań, więc jest to bardzo ważne.
Najczęściej używa się dwóch metod.
Metoda Series.map() przyjmuje funkcję przekształcającą pojedynczą wartość w inną pojedynczą wartość, stosuje ją zbiorczo do wszystkich wartości w danej Series, a następnie zwraca nową Series. Przykładowo, jeśli chcesz odjąć średnią od wszystkich ocen win, aby otrzymać odchylenia:
1
2
review_points_mean = reviews.points.mean()
reviews.points.map(lambda p: p - review_points_mean)
1
2
3
4
5
6
0 -1.447138
1 -1.447138
...
129969 1.552862
129970 1.552862
Name: points, Length: 129971, dtype: float64
Metoda DataFrame.apply() służy wtedy, gdy chcesz wywołać własną funkcję dla każdego wiersza i zastosować przekształcenie do całego DataFrame.
1
2
3
4
5
def remean_points(row):
row.points = row.points - review_points_mean
return row
reviews.apply(remean_points, axis='columns')
Jeśli wywołasz apply() z parametrem axis='index', możesz zastosować funkcję nie do wierszy, lecz do kolumn.
Zarówno Series.map(), jak i DataFrame.apply() zwracają odpowiednio nową (przekształconą) Series i DataFrame, nie modyfikując oryginalnych danych.
| Metoda | Series.map() | DataFrame.apply() |
|---|---|---|
| Obiekt docelowy | Series | DataFrame |
| Jednostka zastosowania | Zastosowanie do pojedynczych wartości (jeśli traktować Series jako wektor kolumnowy, to zastosowanie „wierszami”) | Domyślnie zastosowanie do wierszy po ustawieniu opcji możliwe też do kolumn |
Dla porządku: istnieją też
Series.apply()orazDataFrame.map().
Series.apply():
by_row='compat'(domyślnie): działa tak samo jakSeries.map()by_row=False: przekazuje całą Series jako jedno wejście funkcji (podobnie do działaniaDataFrame.apply()dlaaxis='index')DataFrame.map(): stosuje funkcję do pojedynczych wartości w DataFrame (analogicznie doSeries.map(), z tą różnicą, że obiektem jest DataFrame, a nie Series)
W praktyce Pandas wspiera wiele często używanych odwzorowań „wbudowanie”. Przykład z poprzedniej sekcji można zrealizować dużo prościej, a Pandas i tak poprawnie zinterpretuje intencję:
1
2
review_points_mean = reviews.points.mean()
reviews.points - review_points_mean
1
2
3
4
5
6
0 -1.447138
1 -1.447138
...
129969 1.552862
129970 1.552862
Name: points, Length: 129971, dtype: float64
Co więcej, Pandas wspiera też operacje pomiędzy Series o tej samej długości. W przykładzie z winami można np. połączyć tekstowo informację o kraju i regionie produkcji:
1
reviews.country + " - " + reviews.region_1
1
2
3
4
5
6
0 Italy - Etna
1 NaN
...
129969 France - Alsace
129970 France - Alsace
Length: 129971, dtype: object
Te operacje są szybsze niż map() czy apply(), ponieważ wykorzystują wbudowane mechanizmy przyspieszania obliczeń w Pandas. Pandas potrafi działać w ten sposób dla wszystkich standardowych operatorów Pythona (>, <, == itd.). Mimo to map() i apply() są bardziej elastyczne i pozwalają wykonać bardziej złożone zadania — warto więc je znać.

