TLDR: pandas-log umożliwia logowanie, przydatne zwłaszcza w długich łańcuchach operacji w pandas
Pandas to wspaniała biblioteka, zwłaszcza dla osób, które miały doświadczenie w SQLu i chcą je wykorzystać, pracując z danymi w Pythonie. Pozwala na pracę na tabelarycznych danych w wygodny sposób.
Podobnie jednak jak w SQLu, czasami przychodzi moment, gdy podczas przetwarzania dzieje się coś dziwnego i nie do końca wiadomo, co poszło źle.
Załóżmy że pracujemy na tym samym datasecie graczy NBA z tego sezonu co w poprzednim wpisie i chcemy znaleźć drużynę, której pozycja centra jest najsilniej obsadzona.
import pandas as pd
from basketball_reference_web_scraper import client
players = pd.DataFrame(client.players_season_totals(season_end_year=2020))
players['team'] = players['team'].astype(str).str[5:].str.replace("_", " ")
players['positions'] = players['positions'].astype(str).str.extract("'([^']*)'")
columns = ['made_field_goals', 'attempted_field_goals', 'made_three_point_field_goals',
'attempted_three_point_field_goals', 'made_free_throws',
'attempted_free_throws', 'offensive_rebounds', 'defensive_rebounds',
'assists', 'steals', 'blocks', 'turnovers', 'personal_fouls', 'points']
for stat in columns:
players[stat+"_per_game"] = players[stat] / players["games_played"]
for stat in columns:
players[stat+"_per_36"] = players[stat] / players["minutes_played"] * 36.
Mając przygotowany dataset, chcemy przeprowadzić kilka operacji:
– wybrać graczy, którzy są centrami
– odfiltrować graczy, którzy rozegrali mniej niż 15 spotkań (gracze, ze „zbyt małą próbką”, aby coś o nich powiedzieć)
– wybrać 30 graczy z największą liczbą punktów na 36 minut
– policzyć w których zespołach znajdują się tacy zawodnicy
– uszeregować zespoły według ich liczby
Na podstawie tych kryteriów przygotowujemy sobie kod:
teams = players.copy()\
.query("positions=='center'")\
.query("games_played>15")\
.nlargest(30, "points_per_36")\
.groupby("team")["points_per_36"]\
.count()\
.sort_values(ascending = False)
i… okazuje się, że tak przygotowany kod zwraca nam pusty zbiór. Możemy oczywiście rozbijać i analizować każdą z operacji, ale możemy wykorzystać pakiet pandas-log, który nam w tym wydatnie pomoże
!pip install pandas_log
import pandas_log
with pandas_log.enable():
teams = players.copy()\
.query("positions=='center'")\
.query("games_played>15")\
.nlargest(30, "points_per_36")\
.groupby("team")["points_per_36"]\
.count()\
.sort_values(ascending = False)
Wywołanie przygotowanego przez nas polecenia „z” pandas_log.enable() pokaże nam co się tam właściwie stało:
1) query(expr="positions=='center'", inplace=False):
Metadata:
* Removed 589 rows (100.0%), 0 rows remaining.
Execution Stats:
* Execution time: Step Took 0.006072 seconds.
2) query(expr="games_played>15", inplace=False):
Metadata:
* No change in number of rows of input df.
Execution Stats:
* Execution time: Step Took 0.005762 seconds.
Tips:
* Number of rows didn't change, if you are working on the entire dataset you can remove this operation.
3) nlargest(n=30, columns="points_per_36", keep='first'):
Metadata:
* Picked 30 largest rows by columns (points_per_36).
Execution Stats:
* Execution time: Step Took 0.008734 seconds.
Tips:
* Number of rows didn't change, if you are working on the entire dataset you can remove this operation.
4) groupby(by="team", axis=0, level=None, as_index:bool=True, sort:bool=True, group_keys:bool=True, squeeze:bool=False, observed:bool=False):
Metadata:
* Grouping by team resulted in 0 groups like ,
and more.
Execution Stats:
* Execution time: Step Took 0.001026 seconds.
Jak widać, już w pierwszym poleceniu było coś nie tak (0 rows remaining). Po dokładniejszym przyjrzeniu się okazuje się, że wpisaliśmy nazwę pozycji używając małych liter.
Poprawienie tego błędu:
with pandas_log.enable():
teams = players.copy()\
.query("positions=='CENTER'")\
.query("games_played>15")\
.nlargest(30, "points_per_36")\
.groupby("team")["points_per_36"]\
.count()\
.sort_values(ascending = False)
co w logach wygląda to już na poprawne:
1) query(expr="positions=='CENTER'", inplace=False):
Metadata:
* Removed 485 rows (82.34295415959252%), 104 rows remaining.
Execution Stats:
* Execution time: Step Took 0.007955 seconds.
2) query(expr="games_played>15", inplace=False):
Metadata:
* Removed 18 rows (17.307692307692307%), 86 rows remaining.
Execution Stats:
* Execution time: Step Took 0.007225 seconds.
3) nlargest(n=30, columns="points_per_36", keep='first'):
Metadata:
* Picked 30 largest rows by columns (points_per_36).
Execution Stats:
* Execution time: Step Took 0.006591 seconds.
4) groupby(by="team", axis=0, level=None, as_index:bool=True, sort:bool=True, group_keys:bool=True, squeeze:bool=False, observed:bool=False):
Metadata:
* Grouping by team resulted in 20 groups like
ATLANTA HAWKS,
BOSTON CELTICS,
CHARLOTTE HORNETS,
DALLAS MAVERICKS,
DENVER NUGGETS,
and more.
Execution Stats:
* Execution time: Step Took 0.001055 seconds.
Możemy więc zobaczyć upragnioną listę:
team | count | |
---|---|---|
0 | PHOENIX SUNS | 4 |
1 | MINNESOTA TIMBERWOLVES | 3 |
2 | NEW ORLEANS PELICANS | 2 |
3 | CHARLOTTE HORNETS | 2 |
4 | LOS ANGELES CLIPPERS | 2 |
5 | MEMPHIS GRIZZLIES | 2 |
6 | WASHINGTON WIZARDS | 2 |
7 | ORLANDO MAGIC | 1 |
8 | NEW YORK KNICKS | 1 |
9 | PHILADELPHIA 76ERS | 1 |
10 | UTAH JAZZ | 1 |
11 | PORTLAND TRAIL BLAZERS | 1 |
12 | SAN ANTONIO SPURS | 1 |
13 | INDIANA PACERS | 1 |
14 | DETROIT PISTONS | 1 |
15 | DENVER NUGGETS | 1 |
16 | DALLAS MAVERICKS | 1 |
17 | TORONTO RAPTORS | 1 |
18 | BOSTON CELTICS | 1 |
19 | ATLANTA HAWKS | 1 |