使用Pandas和Python探索数据集

3周前 102次点击 来自 其他

收录专题: Python边学边译

标签: Python

原文链接:Using Pandas and Python to Explore Your Dataset

使用Pandas Python库

在本教程中,您将使用由FiveThirtyEight提供CSV文件,分析NBA比赛结果。创建一个脚本来下载数据:download_nba_all_elo.py :

import requests

download_url = "https://raw.githubusercontent.com/fivethirtyeight/data/master/nba-elo/nbaallelo.csv"
target_csv_path = "nba_all_elo.csv"

response = requests.get(download_url)
response.raise_for_status()    # Check that the request was successful
with open(target_csv_path, "wb") as f:
    f.write(response.content)
print("Download ready.")

执行脚本时,它将把文件保存nba_all_elo.csv在当前工作目录中。

使用Pandas Python库来查看数据:

>>> import pandas as pd
>>> nba = pd.read_csv("nba_all_elo.csv")
>>> type(nba)
<class 'pandas.core.frame.DataFrame'>

您可以看到其中nba包含多少数据:

>>> len(nba)
126314
>>> nba.shape
(126314, 23)

您可以使用Python内置函数len()来确定行数,使用.shape属性查看DataFrame尺寸,结果是一个包含行和列数的元组。

如何确定数据集确实包含篮球统计数据?您可以使用以下内容查看前五行.head():

>>> nba.head()

您可以将Pandas配置为显示所有23列,如下所示:

>>> pd.set_option("display.max.columns", None)

小数位精确到后2位:

>>> pd.set_option("display.precision", 2)

同理,如果你想查看最后5行的数据可以使用.tail() :

>>> nba.tail()

只看到最后3行数据:

>>> nba.tail(3)

显示数据类型

您可以使用以下命令显示所有列及其数据类型.info():

>>> nba.info()

您将看到数据集中所有列的列表以及每个列包含的数据类型。在这里,你可以看到的数据类型int64float64以及object。Pandas使用NumPy库处理这些类型。

显示基本统计

现在,您已经了解了数据集中的数据类型,现在该概述每个列包含的值了。您可以使用.describe()

>>> nba.describe()

此函数为您显示所有数字列的一些基本描述统计信息:

Pandas DataFrame .describe

.describe() 默认情况下仅分析数字列,但是如果使用include参数,则可以提供其他数据类型:

>>> import numpy as np
>>> nba.describe(include=np.object)

因为它们主要包含文本字符串,所以.describe() 不会尝试计算object列的平均值或标准差。但是,它仍将显示一些描述性统计信息:

Pandas DataFrame .describe() with include=np.object

看看team_id和fran_id列。您的数据集包含104个不同的团队ID,但只有53个不同的特许经营ID。此外,最频繁的团队ID是BOS,但最频繁的特许经营ID Lakers。那怎么可能?您需要更多地探索数据集才能回答这个问题。

探索数据集

探索性数据分析可以帮助您回答有关数据集的问题。例如,您可以检查特定值在列中出现的频率:

>>> nba["team_id"].value_counts()
BOS    5997
NYK    5769
LAL    5078
...
SDS      11
>>> nba["fran_id"].value_counts()
Name: team_id, Length: 104, dtype: int64
Lakers          6024
Celtics         5997
Knicks          5769
...
Huskies           60
Name: fran_id, dtype: int64

"Lakers"打了6024场比赛,但其中只有5078场是洛杉矶湖人队的比赛。让我们来找出另一个"Lakers"球队是谁:

>>> nba.loc[nba["fran_id"] == "Lakers", "team_id"].value_counts()
LAL    5078
MNL     946
Name: team_id, dtype: int64

实际上,明尼阿波利斯湖人("MNL")参加了946场比赛。您甚至可以找出他们比赛时间:

>>> nba.loc[nba["team_id"] == "MNL", "date_game"].min()
'1/1/1949'
>>> nba.loc[nba["team_id"] == "MNL", "date_game"].max()
'4/9/1959'
>>> nba.loc[nba["team_id"] == "MNL", "date_game"].agg(("min", "max"))
min    1/1/1949
max    4/9/1959
Name: date_game, dtype: object

您还发现了"BOS"波士顿凯尔特人队为什么在数据集中拥有最多数据的原因。让我们来找出此数据集中包含的所有比赛期间,波士顿凯尔特人队得分了多少分:

>>> nba.loc[nba["team_id"] == "BOS", "pts"].sum()
626484

使用.loc和.iloc

有如下Series对象:

>>> colors = pd.Series(
...     ["red", "purple", "blue", "green", "yellow"],
...     index=[1, 2, 3, 5, 8]
... )
>>> colors
1       red
2    purple
3      blue
5     green
8    yellow
dtype: object

如果使用colors[1]会得到什么结果?Pandas Python库提供了两种数据访问方法:

  1. .loc 指标签索引。
  2. .iloc 指位置索引。

这些数据访问方法更具可读性:

>>> colors.loc[1]
'red'
>>> colors.iloc[1]
'purple'

下图标识为.loc.iloc 的指向:

Pandas Series iloc vs loc

使用 .iloc 获取nba数据集倒数第二行数据:

>>> nba.iloc[-2]
gameorder               63157
game_id          201506170CLE
lg_id                     NBA
_iscopy                     0
year_id                  2015
date_game           6/16/2015
seasongame                102
is_playoffs                 1
team_id                   CLE
fran_id             Cavaliers
pts                        97
elo_i                 1700.74
elo_n                 1692.09
win_equiv             59.2902
opp_id                    GSW
opp_fran             Warriors
opp_pts                   105
opp_elo_i             1813.63
opp_elo_n             1822.29
game_location               H
game_result                 L
forecast              0.48145
notes                     NaN
Name: 126312, dtype: object

选择标签5555到5559的数据,并过滤其中的数据列:

>>> nba.loc[5555:5559, ["fran_id", "opp_fran", "pts", "opp_pts"]]

最后得到的数据:

Pandas DataFrame .loc

查询数据集

您已经了解了如何根据索引访问大型数据集的子集。现在,您将基于数据集列中的值,选择行用来查询数据。例如,您可以创建一个DataFrame仅包含2010年之后的比赛数据:

>>> current_decade = nba[nba["year_id"] > 2010]
>>> current_decade.shape
(12658, 23)

您还可以选择特定字段不为空的行:

>>> games_with_notes = nba[nba["notes"].notnull()]
>>> games_with_notes.shape
(5424, 23)

您甚至可以访问数据类型为str的值,并对它们执行字符串方法:

>>> ers = nba[nba["fran_id"].str.endswith("ers")]
>>> ers.shape
(27797, 23)

str.endswith()用来过滤数据集并查找主队名称以"ers"结尾的所有比赛。

让我们来试试更复杂的过滤查询,搜索两支球队得分均超过100分,team_id为巴尔的摩的比赛:

>>> nba[
...     (nba["_iscopy"] == 0) &
...     (nba["pts"] > 100) &
...     (nba["opp_pts"] > 100) &
...     (nba["team_id"] == "BLB")
... ]

其中,nba["_iscopy"] == 0标识包含不是副本的条目:

Pandas DataFrame query with multiple criteria

另外一个查询实例,查询1992年春,来自洛杉矶的两支球队,在别的球场打了一场主场比赛。查询数据集以找到这两个比赛。两支球队的ID均以开头"LA"。

您可以使用.str查找以开头的团队ID "LA",并且可以假定这种不寻常的比赛会有一些注意事项:

>>> nba[
...     (nba["_iscopy"] == 0) &
...     (nba["team_id"].str.startswith("LA")) &
...     (nba["year_id"]==1992) &
...     (nba["notes"].notnull())
... ]

您的输出应该在1992年5月3日显示两场比赛:

Pandas DataFrame query with multiple criteria: solution of the exercise

分组和汇总数据

Pandas Python库提供了分组和聚合功能,来帮助您完成例如求一组元素的总和,均值或平均值等需求。

例如,获取所有比赛分数总和:

>>> points = nba["pts"]
>>> type(points)
<class 'pandas.core.series.Series'>
>>> points.sum()
12976235

按队名分组,计算分数总和:

>>> nba.groupby("fran_id", sort=False)["pts"].sum()
fran_id
Huskies           3995
Knicks          582497
Stags            20398
Falcons           3797
Capitols         22387
...

默认情况下,Pandas在.groupby()的调用过程中会对组键进行排序。如果您不想排序,请设置sort=False,此参数可以提高性能。

您还可以按多列分组:

>>> nba[
...     (nba["fran_id"] == "Spurs") &
...     (nba["year_id"] > 2010)
... ].groupby(["year_id", "game_result"])["game_id"].count()
year_id  game_result
2011     L              25
 W              63
2012     L              20
 W              60
2013     L              30
 W              73
2014     L              27
 W              78
2015     L              31
 W              58
Name: game_id, dtype: int64

现在,让我们来看看金州勇士队的2014-15赛季(year_id: 2015),他们在常规赛和季后赛中,得多少分,胜负如何?

首先,您可以按"is_playoffs"字段分组,然后按结果分组:

>>> nba[
...     (nba["fran_id"] == "Warriors") &
...     (nba["year_id"] == 2015)
... ].groupby(["is_playoffs", "game_result"])["game_id"].count()
is_playoffs  game_result
0            L              15
             W              67
1            L               5
             W              16

is_playoffs=0显示常规赛的结果,is_playoffs=1显示季后赛的结果。

操作列

您需要知道如何在数据分析过程的不同阶段中操作数据集的列。您可以在初始数据清理阶段添加列或删除列,也可以稍后基于分析的见解来添加和删除列。

创建原始副本DataFrame以使用:

>>> df = nba.copy()
>>> df.shape
(126314, 23)

您可以基于现有列定义新列:

>>> df["difference"] = df.pts - df.opp_pts
>>> df.shape
(126314, 24)

在这里,您使用"pts"和"opp_pts"列创建了一个名为的新列"difference"。此新列具有与旧列相同的功能:

>>> df["difference"].max()
68

在这里,您使用了聚合函数.max()来查找新列的最大值。

您还可以重命名数据集的列:

>>> renamed_df = df.rename(
...     columns={"game_result": "result", "game_location": "location"}
... )
>>> renamed_df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 126314 entries, 0 to 126313
Data columns (total 24 columns):
gameorder      126314 non-null int64
...
location       126314 non-null object
result         126314 non-null object
forecast       126314 non-null float64
notes          5424 non-null object
difference     126314 non-null int64
dtypes: float64(6), int64(8), object(10)
memory usage: 23.1+ MB

您的数据集可能包含不需要的列。例如,对于某些人来说,Elo评分可能是一个有趣的概念,但是您不会在本教程中对其进行分析。您可以删除与Elo相关的四列:

>>> df.shape
(126314, 24)
>>> elo_columns = ["elo_i", "elo_n", "opp_elo_i", "opp_elo_n"]
>>> df.drop(elo_columns, inplace=True, axis=1)
>>> df.shape
(126314, 20)

记住,您在前面的示例中添加了新列"difference",使列总数达到24。删除四个Elo列时,列总数下降为20。

清理数据

通常,在继续进行更复杂的分析之前,您会认真检查数据集以解决所有问题。我们将在本节尝试清理下数据。

缺失值

首先,我们使用“nba.info()”检查数据集,您会发现列notes的大多数行都包含空值:
Pandas DataFrame .info()

此输出显示该notes列只有5424个非空值。这意味着您的数据集中超过120,000行在此列中具有空值。

有时,处理包含缺失值的记录的最简单方法是忽略它们。您可以使用.dropna()删除所有缺少值的行:

>>> rows_without_missing_data = nba.dropna()
>>> rows_without_missing_data.shape
(5424, 23)

当然,这种数据清理对您的nba数据集没有意义,因为缺少注释对比赛来说不是问题。但是,如果您的数据集包含一百万条有效记录,而一百条缺少相关数据,那么删除不完整的记录可能是一个合理的解决方案。

如果,您认为note列可有可无,那么可使用 axis=1参数剔除数据列:

>>> data_without_missing_columns = nba.dropna(axis=1)
>>> data_without_missing_columns.shape
(126314, 22)

如果您的用例有一个有意义的默认值,那么您也可以用以下值替换缺少的值:

>>> data_with_default_notes = nba.copy()
>>> data_with_default_notes["notes"].fillna(
...     value="no notes at all",
...     inplace=True
... )
>>> data_with_default_notes["notes"].describe()
count              126314
unique                232
top       no notes at all
freq               120890
Name: notes, dtype: object

无效值

无效值甚至比缺失值更危险。通常,您可以按预期执行数据分析,但是获得的结果却很奇怪。如果您的数据集非常庞大或使用了手动输入,这一点尤其重要。无效值的检测通常更具挑战性,但是您可以使用查询和聚合来执行一些健全性检查。

您可以做的一件事就是验证数据范围。为此,.describe()非常方便。回想一下它返回以下输出:

Pandas DataFrame .describe()

year_id1947年到2015年的值好像有些不正常?

那pts呢? 最小值是否可为 0 ?让我们来看看

>>> nba[nba["pts"] == 0]

该查询返回一行:

Pandas DataFrame query

根据您的分析,您可能希望将其从数据集中删除。

值不一致

您可以定义一些互斥的查询条件,并确认这些条件不会一起出现。

在NBA的数据集,该字段的值ptsopp_ptsgame_result应该是相互一致。您可以使用以下.empty属性进行检查:

>>> nba[(nba["pts"] > nba["opp_pts"]) & (nba["game_result"] != 'W')].empty
True
>>> nba[(nba["pts"] < nba["opp_pts"]) & (nba["game_result"] != 'L')].empty
True

幸运的是,这两个查询都返回一个空的DataFrame.

合并多个数据集

在上一节中,您已经学习了如何清理凌乱的数据集。现实世界数据的另一个方面是,它通常分成多个部分。您将学习如何获取这些片段并将它们组合到一个可供分析的数据集中。

您手上有一个城市数据集city_data:

>>> city_data = pd.DataFrame({
...     "revenue": city_revenues,
...     "employee_count": city_employee_count
... })
>>> city_data
           revenue  employee_count
Amsterdam     4200             5.0
Tokyo         6500             8.0
Toronto       8000             NaN

假设您设法在另外两个城市收集了一些数据:

>>> further_city_data = pd.DataFrame(
...     {"revenue": [7000, 3400], "employee_count":[2, 2]},
...     index=["New York", "Barcelona"]
... )

您可以使用.concat()将这些城市添加到city_data:

>>> all_city_data = pd.concat([city_data, further_city_data], sort=False)
>>> all_city_data
Amsterdam   4200    5.0
Tokyo       6500    8.0
Toronto     8000    NaN
New York    7000    2.0
Barcelona   3400    2.0

可视化

本节内容需要在Jupyter notebook环境下学习,且为可选内容。

启用matplotlib,需在Jupyter notebook添加如下行 :

%matplotlib inline

Series和DataFrame对象都有一个.plot()方法,它是matplotlib.pyplot.plot()的包装。默认情况下,它会创建一个折线图。

例如,可视化尼克斯整个赛季得分了多少分:

>>> nba[nba["fran_id"] == "Knicks"].groupby("year_id")["pts"].sum().plot()

Pandas plot line

您还可以创建其他类型的图,例如条形图:

>>> nba["fran_id"].value_counts().head(10).plot(kind="bar")

这将显示出拥有最多比赛次数的特许经营权:

Pandas plot bar

湖人队以最小的优势领先凯尔特人,还有另外六支球队的比赛人数超过5000。

现在尝试更复杂的例子。2013年,迈阿密热火队获得了冠军。创建一个饼图,显示他们在该季节中的胜利和失败次数:

首先,您定义一个条件,使其仅包括2013年以来的热火比赛。然后,以与上面相同的方式可视化数据:

>>> nba[
...     (nba["fran_id"] == "Heat") &
...     (nba["year_id"] == 2013)
... ]["game_result"].value_counts().plot(kind="pie")

这是冠军派的样子:

Pandas plot pie

结论

在本教程中,您学习了如何使用Pandas探索数据集。您了解了如何访问特定的行和列。通过指定列的数据类型,处理缺失值,清除数据等等多种技术。您甚至已经基于这些内容创建了查询,聚合和绘图。

Card image cap
开发者雷

尘世间一个小小的开发者,每天增加一些无聊的知识,就不会无聊了

要加油~~~

技术文档 >> 系列应用 >>
热推应用
Let'sLearnSwift
学习Swift的入门教程
PyPie
Python is as good as Pie
标签