个性化阅读
专注于IT技术分析

使用Pandas和Python研究你的数据集

本文概述

你是否有一个大型数据集, 其中充满了有趣的见解, 但不确定从哪里开始探索?你的老板问过你要从中生成一些统计信息, 但是提取它们不是那么容易吗?这些正是Pandas和Python可以帮助你的用例!使用这些工具, 你将能够将大型数据集切成可管理的部分, 并从这些信息中收集见解。

在本教程中, 你将学习如何:

  • 计算有关数据的指标
  • 执行基本查询和汇总
  • 发现和处理不正确的数据, 不一致和缺失的值
  • 使用图表可视化数据

你还将了解Pandas和Python使用的主要数据结构之间的差异。接下来, 你可以通过以下链接获得本教程中的所有示例代码:

获取Jupyter笔记本:单击此处以获取Jupyter笔记本, 你将在本教程中使用它来通过Pandas探索数据。

移除广告

设置环境

开始学习本教程需要做一些事情。首先是熟悉Python的内置数据结构, 尤其是列表和字典。有关更多信息, 请查看Python中的列表和元组以及Python中的字典。

你需要的第二件事是可以正常工作的Python环境。你可以在安装了Python 3的任何终端中进行操作。如果你希望看到更好的输出, 尤其是对于将要使用的大型NBA数据集, 则可以在Jupyter笔记本中运行示例。

注意:如果你根本没有安装Python, 请查阅《 Python 3安装和设置指南》。你也可以在试用版的Jupyter笔记本电脑上在线关注。

你需要的最后一件事是Pandas Python库, 你可以使用pip进行安装:

$ python -m pip install pandas

你还可以使用Conda软件包管理器:

$ conda install pandas

如果你使用的是Anaconda发行版, 那就太好了! Anaconda已经安装了Pandas Python库。

注意:你是否听说过Python世界中有多个软件包管理器, 并且对选择哪个软件包感到有些困惑? pip和conda都是绝佳的选择, 它们各有千秋。

如果你打算将Python主要用于数据科学工作, 那么conda也许是更好的选择。在conda生态系统中, 你有两个主要选择:

  1. 如果你想要一个稳定的数据科学环境并快速运行, 并且不介意下载500 MB数据, 请查看Anaconda发行版。
  2. 如果你希望使用更简单的设置, 请查看在Windows上为机器学习设置Python中有关安装Miniconda的部分。

本教程中的示例已通过Python 3.7和Pandas 0.25.0进行了测试, 但它们也应在较旧的版本中运行。单击下面的链接, 可以在Jupyter笔记本中获得本教程中将看到的所有代码示例:

获取Jupyter笔记本:单击此处以获取Jupyter笔记本, 你将在本教程中使用它来通过Pandas探索数据。

让我们开始吧!

使用Pandas Python库

现在, 你已经安装了Pandas, 现在该看看数据集了。在本教程中, 你将在17MB CSV文件中分析FiveThirtyEight提供的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保存在当前工作目录中。

注意:你也可以使用网络浏览器下载CSV文件。

但是, 拥有下载脚本有几个优点:

  • 你可以说出数据的来源。
  • 你可以随时重复下载!如果经常刷新数据, 则特别方便。
  • 你无需与同事共享17MB CSV文件。通常, 共享下载脚本就足够了。

现在, 你可以使用Pandas Python库来查看数据:

>>>

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

在这里, 你遵循使用pd别名在Python中导入Pandas的约定。然后, 使用.read_csv()读取数据集并将其作为DataFrame对象存储在变量nba中。

注意:你的数据不是CSV格式吗?别担心! Pandas Python库提供了几个类似的功能, 例如read_json(), read_html()和read_sql_table()。要了解如何使用这些文件格式, 请查看“使用Pandas读取和写入文件”或查阅文档。

你可以看到nba包含多少数据:

>>>

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

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

现在你知道数据集中有126, 314行和23列。但是, 如何确定数据集确实包含篮球统计数据?你可以使用.head()查看前五行:

>>>

>>> nba.head()

如果你正在跟随Jupyter笔记本电脑, 那么你将看到如下结果:

Pandas DataFrame .head()

除非你的屏幕很大, 否则你的输出可能不会显示全部23列。在中间的某个地方, 你会看到一列省略号(…), 表示缺少数据。如果你在终端机中工作, 那可能比包装长行更具可读性。但是, Jupyter笔记本将允许你滚动。你可以将Pandas配置为显示所有23列, 如下所示:

>>>

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

虽然查看所有列都是可行的, 但你可能不需要六个小数位!将其更改为两个:

>>>

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

要确认你已成功更改选项, 可以再次执行.head(), 也可以使用.tail()显示最后五行:

>>>

>>> nba.tail()

现在, 你应该看到所有列, 并且数据应该显示两个小数位:

Pandas DataFrame .tail()

只需进行少量练习, 即可发现.head()和.tail()的其他可能性。你可以打印DataFrame的最后三行吗?展开下面的代码块以查看解决方案:

解决方案:头和尾显示/隐藏

以下是打印nba的最后三行的方法:

>>>

>>> nba.tail(3)

你的输出应如下所示:

带有参数的Pandas DataFrame .tail()

你可以使用上面设置的选项查看数据集的最后三行。

与Python标准库类似, Pandas中的函数也带有几个可选参数。每当你遇到一个看起来很相关但与你的用例稍有不同的示例时, 请查看官方文档。通过调整一些可选参数, 你很有可能找到解决方案!

移除广告

了解你的数据

你已使用Pandas Python库导入了CSV文件, 并首先查看了数据集的内容。到目前为止, 你仅看到了数据集的大小及其前几行和后几​​行。接下来, 你将学习如何更系统地检查数据。

显示数据类型

了解数据的第一步是发现其包含的不同数据类型。尽管你可以将任何内容放入列表中, 但DataFrame的列包含特定数据类型的值。比较Pandas和Python数据结构时, 你会发现这种行为使Pandas更快!

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

>>>

>>> nba.info()

这将产生以下输出:

Pandas DataFrame .info()

你会看到数据集中所有列的列表以及每个列包含的数据类型。在这里, 你可以看到数据类型int64, float64和object。Pandas使用NumPy库处理这些类型。稍后, 你将遇到Pandas Python库自行实现的更为复杂的分类数据类型。

对象数据类型是一种特殊的数据类型。根据《 Pandas Cookbook》的说法, 对象数据类型是“ Pandas无法识别为任何其他特定类型的列的全部内容”。实际上, 这通常意味着该列中的所有值都是字符串。

尽管你可以在对象数据类型中存储任意Python对象, 但你应该意识到这样做的弊端。对象列中的奇怪值可能会损害Pandas的性能及其与其他库的互操作性。有关更多信息, 请查看官方的入门指南。

显示基础统计

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

>>>

>>> nba.describe()

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

Pandas DataFrame .describe()

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

>>>

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

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

具有include np.object的Pandas DataFrame .describe()

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

移除广告

探索你的数据集

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

>>>

>>> 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

一支名为“湖人队”的球队似乎打了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

看起来好像明尼阿波利斯湖人队在1949年到1959年之间效力。

你还发现了波士顿凯尔特人队“ BOS”为什么在数据集中玩最多的游戏。让我们再分析一下他们的历史。找出此数据集中包含的所有比赛期间, 波士顿凯尔特人队得分了多少分。展开下面的代码块以获取解决方案:

解决方案:DataFrame简介显示/隐藏

与.min()和.max()聚合函数类似, 你也可以使用.sum():

>>>

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

波士顿凯尔特人队总计获得626484点。

你对Pandas DataFrame的功能有所了解。在以下各节中, 你将扩展刚刚使用的技术, 但首先, 你将放大并了解这种强大的数据结构的工作原理。

了解Pandas的数据结构

尽管DataFrame提供的功能听起来很直观, 但是基本概念却很难理解。因此, 你将保留庞大的NBA DataFrame, 并从头开始构建一些较小的Pandas对象。

了解系列对象

Python的最基本数据结构是列表, 这也是了解pandas.Series对象的一个​​很好的起点。根据列表创建一个新的Series对象:

>>>

>>> revenues = pd.Series([5555, 7000, 1980])
>>> revenues
0    5555
1    7000
2    1980
dtype: int64

你已经使用列表[5555, 7000, 1980]创建了一个称为收入的系列对象。 Series对象包含两个组件:

  1. 一系列值
  2. 标识符序列, 即索引

你可以分别使用.values和.index访问这些组件:

>>>

>>> revenues.values
array([5555, 7000, 1980])
>>> revenues.index
RangeIndex(start=0, stop=3, step=1)

Revenues.values返回系列中的值, 而Revenues.index返回位置索引。

注意:如果你熟悉NumPy, 那么可能会感兴趣的是, 注意到Series对象的值实际上是n维数组:

>>>

>>> type(revenues.values)
<class 'numpy.ndarray'>

如果你不熟悉NumPy, 则无需担心!你可以仅使用Pandas Python库来探索数据集的来龙去脉。但是, 如果你对Pandas在后台执行的操作感到好奇, 请查看Look Ma, 没有麻烦:使用NumPy进行数组编程。

虽然Pandas建立在NumPy之上, 但它们的索引却有显着差异。就像NumPy数组一样, Pandas系列也有一个隐式定义的整数索引。此隐式索引指示元素在系列中的位置。

但是, 系列也可以具有任意类型的索引。你可以将此显式索引视为特定行的标签:

>>>

>>> city_revenues = pd.Series(
...     [4200, 8000, 6500], ...     index=["Amsterdam", "Toronto", "Tokyo"]
... )
>>> city_revenues
Amsterdam    4200
Toronto      8000
Tokyo        6500
dtype: int64

在这里, 索引是由字符串表示的城市名称的列表。你可能已经注意到Python字典也使用字符串索引, 这是一个容易记住的类比!你可以使用上面的代码块来区分两种类型的系列:

  1. 收益:该系列的行为类似于Python列表, 因为它只有一个位置索引。
  2. city_revenues:此系列的行为类似于Python词典, 因为它同时具有位置索引和标签索引。

以下是通过Python词典构建带有标签索引的系列的方法:

>>>

>>> city_employee_count = pd.Series({"Amsterdam": 5, "Tokyo": 8})
>>> city_employee_count
Amsterdam    5
Tokyo        8
dtype: int64

词典键成为索引, 词典值是系列值。

就像字典一样, Series也支持.keys()和in关键字:

>>>

>>> city_employee_count.keys()
Index(['Amsterdam', 'Tokyo'], dtype='object')
>>> "Tokyo" in city_employee_count
True
>>> "New York" in city_employee_count
False

你可以使用这些方法来快速回答有关数据集的问题。

移除广告

了解DataFrame对象

系列是一个非常强大的数据结构, 但它有其局限性。例如, 每个键只能存储一个属性。如你所见, 具有23列的nba数据集, Pandas Python库在其DataFrame中提供了更多功能。该数据结构是一系列共享相同索引的Series对象。

如果你已遵循“系列”示例, 那么你应该已经有了两个以城市为键的“系列”对象:

  1. city_revenues
  2. city_employee_count

你可以通过在构造函数中提供字典来将这些对象组合到DataFrame中。字典键将成为列名, 并且值应包含Series对象:

>>>

>>> 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

请注意, Pandas如何用NaN替换多伦多缺少的employee_count值。

新的DataFrame索引是两个Series索引的并集:

>>>

>>> city_data.index
Index(['Amsterdam', 'Tokyo', 'Toronto'], dtype='object')

就像Series一样, DataFrame还将其值存储在NumPy数组中:

>>>

>>> city_data.values
array([[4.2e+03, 5.0e+00], [6.5e+03, 8.0e+00], [8.0e+03, nan]])

你还可以将DataFrame的2维称为轴:

>>>

>>> city_data.axes
[Index(['Amsterdam', 'Tokyo', 'Toronto'], dtype='object'), Index(['revenue', 'employee_count'], dtype='object')]
>>> city_data.axes[0]
 Index(['Amsterdam', 'Tokyo', 'Toronto'], dtype='object')
>>> city_data.axes[1]
 Index(['revenue', 'employee_count'], dtype='object')

标记为0的轴为行索引, 标记为1的轴为列索引。该术语很重要, 因为你会遇到几种接受轴参数的DataFrame方法。

DataFrame也是类似于字典的数据结构, 因此它也支持.keys()和in关键字。但是, 对于DataFrame, 这些与索引无关, 而与列有关:

>>>

>>> city_data.keys()
Index(['revenue', 'employee_count'], dtype='object')
>>> "Amsterdam" in city_data
False
>>> "revenue" in city_data
True

你可以在更大的NBA数据集中看到这些概念。它是否包含称为“点”的列, 还是称为“ pts”?要回答这个问题, 请显示nba数据集的索引和轴, 然后展开以下代码块以获取解决方案:

解决方案:NBA指数显示/隐藏

由于你在读取CSV文件时未指定索引列, 因此Pandas已为DataFrame分配了RangeIndex:

>>>

>>> nba.index
RangeIndex(start=0, stop=126314, step=1)

像所有DataFrame对象一样, nba具有两个轴:

>>>

>>> nba.axes
[RangeIndex(start=0, stop=126314, step=1), Index(['gameorder', 'game_id', 'lg_id', '_iscopy', 'year_id', 'date_game', 'seasongame', 'is_playoffs', 'team_id', 'fran_id', 'pts', 'elo_i', 'elo_n', 'win_equiv', 'opp_id', 'opp_fran', 'opp_pts', 'opp_elo_i', 'opp_elo_n', 'game_location', 'game_result', 'forecast', 'notes'], dtype='object')]

你可以使用.keys()检查一列是否存在:

>>>

>>> "points" in nba.keys()
False
>>> "pts" in nba.keys()
True

该列称为“点”, 而不是“点”。

当你使用这些方法回答有关数据集的问题时, 请务必记住你是在使用Series还是DataFrame, 以确保你的解释正确。

访问系列元素

在上面的部分中, 你已基于Python列表创建了Pandas系列, 并比较了这两种数据结构。你已经了解了Series对象在几种方面与列表和字典的相似之处。进一步的相似之处在于, 你也可以将索引运算符([])用于Series。

你还将学习如何使用两种特定于Pandas的访问方法:

  1. 的.loc
  2. .iloc

你会发现, 这些数据访问方法比索引运算符更具可读性。

移除广告

使用索引运算符

回忆一下一个系列有两个索引:

  1. 位置索引或隐式索引, 始终为RangeIndex
  2. 标签或显式索引, 可以包含任何可哈希对象

接下来, 重新访问city_revenues对象:

>>>

>>> city_revenues
Amsterdam    4200
Toronto      8000
Tokyo        6500
dtype: int64

你可以方便地使用标签和位置索引访问系列中的值:

>>>

>>> city_revenues["Toronto"]
8000
>>> city_revenues[1]
8000

你也可以使用负索引和切片, 就像使用列表一样:

>>>

>>> city_revenues[-1]
6500
>>> city_revenues[1:]
Toronto    8000
Tokyo      6500
dtype: int64
>>> city_revenues["Toronto":]
Toronto    8000
Tokyo      6500
dtype: int64

如果你想了解有关索引运算符可能性的更多信息, 请查看Python中的列表和元组。

使用.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

颜色[1]将返回什么?对于位置索引, colors [1]为“紫色”。但是, 如果按标签索引进行操作, 则colors [1]表示“红色”。

好消息是, 你不必弄清楚!为了避免混淆, Pandas Python库提供了两种数据访问方法:

  1. .loc表示标签索引。
  2. .iloc是位置索引。

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

>>>

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

colors.loc [1]返回“红色”, 带有标签1的元素。colors.iloc [1]返回“紫色”, 带有索引1的元素。

下图显示了.loc和.iloc所引用的元素:

iloc_vs_loc

同样, .loc指向图像右侧的标签索引。同时, .iloc指向图片左侧的位置索引。

记住.loc和.iloc之间的区别要比弄清楚索引运算符将返回什么要容易得多。即使你熟悉索引运算符的所有怪癖, 也可能会以为每个读取你的代码的人都已经将这些规则内部化了!

注意:除了让Series与数字标签混淆之外, Python索引运算符还存在一些性能缺陷。可以在交互式会话中使用它进行临时分析, 但是对于生产代码, 最好使用.loc和.iloc数据访问方法。有关更多详细信息, 请查阅《 Pandas用户指南》中有关建立索引和选择数据的部分。

.loc和.iloc还支持索引运算符所期望的功能, 例如切片。但是, 这些数据访问方法具有重要的区别。虽然.iloc排除了close元素, 但.loc包括了它。看一下以下代码块:

>>>

>>> # Return the elements with the implicit index: 1, 2
>>> colors.iloc[1:3]
2    purple
3      blue
dtype: object

如果将此代码与上图进行比较, 则可以看到, colors.iloc [1:3]返回位置索引为1和2的元素。位置索引为3的结尾项“ green”将被排除。

另一方面, .loc包含close元素:

>>>

>>> # Return the elements with the explicit index between 3 and 8
>>> colors.loc[3:8]
3      blue
5     green
8    yellow
dtype: object

该代码块说要返回标签索引在3到8之间的所有元素。此处, 结束项“ yellow”的标签索引为8, 并包含在输出中。

你还可以将负位置索引传递给.iloc:

>>>

>>> colors.iloc[-2]
'green'

从系列的结尾开始, 然后返回第二个元素。

注意:曾经有一个.ix索引器, 它试图猜测它是否应该根据索引的数据类型应用位置索引还是标签索引。因为它引起了很多混乱, 所以从Pandas 0.20.0版开始不推荐使用。

强烈建议你不要使用.ix进行索引。相反, 请始终使用.loc进行标签索引, 并使用.iloc进行位置索引。有关更多详细信息, 请查看《Pandas用户指南》。

你可以使用上面的代码块来区分两个系列行为:

  1. 你可以在系列上使用.iloc, 类似于在列表上使用[]。
  2. 你可以在Series上使用.loc, 类似于在字典上使用[]。

在访问Series对象的元素时, 请务必牢记这些区别。

移除广告

访问DataFrame元素

由于DataFrame由Series对象组成, 因此你可以使用完全相同的工具来访问其元素。关键的区别是DataFrame的附加维度。你将对列使用索引运算符, 并对行使用.loc和.iloc访问方法。

使用索引运算符

如果你将DataFrame视为一个字典, 其值为Series, 那么可以使用索引运算符访问其列:

>>>

>>> city_data["revenue"]
Amsterdam    4200
Tokyo        6500
Toronto      8000
Name: revenue, dtype: int64
>>> type(city_data["revenue"])
pandas.core.series.Series

在这里, 你可以使用索引运算符来选择标记为“收入”的列。

如果列名是字符串, 那么你也可以使用带点符号的属性样式访问:

>>>

>>> city_data.revenue
Amsterdam    4200
Tokyo        6500
Toronto      8000
Name: revenue, dtype: int64

city_data [“ revenue”]和city_data.revenue返回相同的输出。

在一种情况下, 使用点表示法访问DataFrame元素可能无法正常工作, 或者会导致意外情况。这是当列名与DataFrame属性或方法名重合时:

>>>

>>> toys = pd.DataFrame([
...     {"name": "ball", "shape": "sphere"}, ...     {"name": "Rubik's cube", "shape": "cube"}
... ])
>>> toys["shape"]
0    sphere
1      cube
Name: shape, dtype: object
>>> toys.shape
(2, 2)

索引操作toys [“ shape”]返回正确的数据, 但是属性样式的操作toys.shape仍返回DataFrame的形状。你只应在交互式会话中或对读取操作使用属性样式的访问。你不应将其用于生产代码或操作数据(例如定义新列)。

使用.loc和.iloc

与Series相似, DataFrame还提供.loc和.iloc数据访问方法。请记住, .loc使用标签, .iloc使用位置索引:

>>>

>>> city_data.loc["Amsterdam"]
revenue           4200.0
employee_count       5.0
Name: Amsterdam, dtype: float64
>>> city_data.loc["Tokyo": "Toronto"]
        revenue employee_count
Tokyo   6500    8.0
Toronto 8000    NaN
>>> city_data.iloc[1]
revenue           6500.0
employee_count       8.0
Name: Tokyo, dtype: float64

每行代码从city_data中选择不同的行:

  1. city_data.loc [“ Amsterdam”]选择标签索引为“ Amsterdam”的行。
  2. city_data.loc [“ Tokyo”:“ Toronto”]从“ Tokyo”到“ Toronto”选择带有标签索引的行。请记住, .loc是包容性的。
  3. city_data.iloc [1]选择位置索引为1的行“ Tokyo”。

好了, 你已经在小型数据结构上使用了.loc和.iloc。现在, 该练习更大的东西了!使用数据访问方法显示nba数据集的倒数第二行。然后, 展开下面的代码块以查看解决方案:

解决方案:NBA访问显示/隐藏行

倒数第二行是位置索引为-2的行。你可以使用.iloc显示它:

>>>

>>> 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

你会看到输出为Series对象。

对于DataFrame, 数据访问方法.loc和.iloc也接受第二个参数。当第一个参数根据索引选择行时, 第二个参数选择列。你可以将这些参数一起使用, 以从DataFrame中选择行和列的子集:

>>>

>>> city_data.loc["Amsterdam": "Tokyo", "revenue"]
Amsterdam    4200
Tokyo        6500
Name: revenue, dtype: int64

请注意, 你用逗号(, )分隔了参数。第一个参数“阿姆斯特丹”:“东京”表示选择这两个标签之间的所有行。第二个参数在逗号后面, 表示选择“收入”列。

现在是时候在更大的nba数据集中看到相同的构造了。选择标签5555和5559之间的所有比赛。你只对球队的名称和得分感兴趣, 因此也请选择这些元素。展开下面的代码块以查看解决方案:

解决方案:NBA访问子集的“显示/隐藏”

首先, 定义要查看的行, 然后列出相关列:

>>>

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

你将.loc用于标签索引, 并使用逗号(, )分隔两个参数。

你应该看到相当庞大的数据集的一小部分:

Pandas DataFrame .loc

输出更容易阅读!

使用.loc和.iloc这样的数据访问方法, 你可以仅选择DataFrame的正确子集来帮助你回答有关数据集的问题。

移除广告

查询数据集

你已经了解了如何根据索引访问大型数据集的子集。现在, 你将根据数据集列中的值选择行以查询数据。例如, 你可以创建一个仅包含2010年之后玩过的游戏的新DataFrame:

>>>

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

你仍然拥有全部23列, 但是新的DataFrame仅由“ year_id”列中的值大于2010的行组成。

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

>>>

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

如果要避免列中缺少任何值, 这将很有帮助。你也可以使用.notna()实现相同的目标。

你甚至可以将对象数据类型的值作为str访问并对其执行字符串方法:

>>>

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

你可以使用.str.endswith()过滤数据集, 并找到主队名称以“ ers”结尾的所有比赛。

你可以组合多个条件并查询数据集。为此, 请确保将每个括号放在括号中并使用逻辑运算符|。和&分隔它们。

注意:运算符和, 或、、 &&和||在这里不会工作。如果你想知道为什么, 请查看有关Pandas Python库如何在Python Pandas中使用布尔运算符的部分:你可能不知道的技巧和功能。

搜索两支球队得分均超过100分的巴尔的摩比赛。为了只看一次每个游戏, 你需要排除重复项:

>>>

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

在这里, 你使用nba [“ _ iscopy”] == 0仅包含不是副本的条目。

你的输出应包含五个重要的游戏:

具有多个条件的Pandas DataFrame查询

尝试使用多个条件构建另一个查询。在1992年春季, 来自洛杉矶的两支球队都不得不在另一个球场打一场主场比赛。查询数据集以找到这两个游戏。两支球队的ID均以“ LA”开头。展开下面的代码块以查看解决方案:

解决方案:查询显示/隐藏

你可以使用.str查找以“ LA”开头的团队ID, 并且可以假定这种不寻常的游戏会有一些注意事项:

>>>

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

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

具有多个条件的Pandas DataFrame查询:练习的解决方案

好发现!

当你知道如何使用多个条件查询数据集时, 便可以回答有关数据集的更具体的问题。

移除广告

分组和汇总数据

你可能还需要学习数据集的其他功能, 例如一组元素的总和, 均值或平均值。幸运的是, Pandas Python库提供了分组和聚合功能来帮助你完成此任务。

系列有二十多种不同的方法来计算描述性统计数据。这里有些例子:

>>>

>>> city_revenues.sum()
18700
>>> city_revenues.max()
8000

第一种方法返回city_revenues的总数, 第二种方法返回最大值。你还可以使用其他方法, 例如.min()和.mean()。

请记住, DataFrame的列实际上是Series对象。因此, 可以在nba的列上使用以下相同的功能:

>>>

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

一个DataFrame可以有多个列, 这为聚合引入了新的可能性, 例如分组:

>>>

>>> 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显示季后赛的结果。

在以上示例中, 你仅涉及了Pandas Python库中可用的聚合函数的表面。要查看有关如何使用它们的更多示例, 请查看《 Pandas GroupBy:Python数据分组指南》。

操作列

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

创建原始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()来查找新列的最大值。

你还可以重命名数据集的列。似乎“ game_result”和“ game_location”太冗长, 因此请立即将其重命名:

>>>

>>> 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

请注意, 有一个新对象, 重命名为df。与其他几种数据操作方法一样, .rename()默认情况下返回一个新的DataFrame。如果要直接操作原始DataFrame, 则.rename()还提供一个就地参数, 你可以将其设置为True。

你的数据集可能包含不需要的列。例如, 对于某些人来说, 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。

移除广告

指定数据类型

当你通过调用构造函数或读取CSV文件创建新的DataFrame时, Pandas会根据其值将数据类型分配给每一列。虽然做得很好, 但并不完美。如果你为列选择正确的数据类型, 则可以显着提高代码的性能。

再看一下nba数据集的列:

>>>

>>> df.info()

你会看到与以前相同的输出:

Pandas DataFrame .info()

你的十列中有数据类型对象。这些对象列中的大多数包含任意文本, 但是也有一些用于数据类型转换的候选对象。例如, 看一下date_game列:

>>>

>>> df["date_game"] = pd.to_datetime(df["date_game"])

在这里, 你使用.to_datetime()将所有游戏日期指定为datetime对象。

其他列包含一些结构更清晰的文本。 game_location列只能具有三个不同的值:

>>>

>>> df["game_location"].nunique()
3
>>> df["game_location"].value_counts()
A    63138
H    63138
N       38
Name: game_location, dtype: int64

你将在关系数据库中为此类列使用哪种数据类型?你可能不会使用varchar类型, 而是使用一个枚举。 Pandas提供了用于相同目的的分类数据类型:

>>>

>>> df["game_location"] = pd.Categorical(df["game_location"])
>>> df["game_location"].dtype
CategoricalDtype(categories=['A', 'H', 'N'], ordered=False)

分类数据相对于非结构化文本具有一些优势。当你指定分类数据类型时, 由于Pandas仅在内部使用唯一值, 因此使验证更加容易并节省了大量内存。总价值与唯一价值之比越高, 你节省的空间就越多。

再次运行df.info()。你应该看到将game_location数据类型从object更改为categorical减少了内存使用。

注意:类别数据类型还使你可以通过.cat访问器访问其他方法。要了解更多信息, 请查看官方文档。

你经常会遇到文本列过多的数据集。数据科学家必须具备的一项基本技能是能够发现可以将哪些列转换为性能更高的数据类型的能力。

现在花点时间练习一下。在nba数据集中找到另一列具有通用数据类型的列, 并将其转换为更具体的列。你可以展开下面的代码块以查看一种潜在的解决方案:

解决方案:指定数据类型显示/隐藏

game_result只能采用两个不同的值:

>>>

>>> df["game_result"].nunique()
2
>>> df["game_result"].value_counts()
L    63157
W    63157

为了提高性能, 你可以将其转换为分类列:

>>>

>>> df["game_result"] = pd.Categorical(df["game_result"])

你可以使用df.info()来检查内存使用情况。

当你处理更多的数据集时, 节省内存就变得尤为重要。在继续探索数据集时, 请务必牢记性能。

移除广告

清洁数据

你可能会惊讶地发现本节太晚了!通常, 在进行更复杂的分析之前, 你需要认真检查数据集以解决所有问题。但是, 在本教程中, 你将依靠在上一节中学到的技术来清理数据集。

缺失值

你是否曾经想过为什么.info()显示一列包含多少非空值?原因是这是至关重要的信息。空值通常表示数据收集过程中存在问题。他们可以使多种分析技术变得困难甚至不可能, 例如不同类型的机器学习。

当你使用nba.info()检查nba数据集时, 你会发现它非常整洁。只有列注释的大部分行都包含空值:

Pandas DataFrame .info()

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

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

>>>

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

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

如果与你的分析无关的问题列, 你也可以删除它们。为此, 请再次使用.dropna()并提供axis = 1参数:

>>>

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

现在, 生成的DataFrame包含所有126, 314个游戏, 但有时不包含空的Notes列。

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

>>>

>>> 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_id在1947年至2015年之间变化。这听起来似乎合理。

那点呢?最小值如何为0?让我们来看看这些游戏:

>>>

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

该查询返回一行:

Pandas DataFrame查询

看来游戏已被没收。根据你的分析, 你可能希望将其从数据集中删除。

价值观不一致

有时, 一个值本身就完全是现实的, 但与其他列中的值不符。你可以定义一些互斥的查询条件, 并确认它们不会同时出现。

在NBA数据集中, 字段pts, opp_pts和game_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。

每当你使用原始数据集时, 都要做好应对意外的准备, 特别是如果它们是从不同来源或通过复杂的渠道收集的。你可能会看到一排球队的得分高于对手, 但仍然没有赢的行-至少根据你的数据集!为了避免出现这种情况, 请确保在Pandas和Python工具库中添加更多的数据清除技术。

合并多个数据集

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

之前, 你已基于其索引将两个Series对象组合到一个DataFrame中。现在, 你将更进一步, 并使用.concat()将city_data与另一个DataFrame结合在一起。假设你设法在另外两个城市收集了一些数据:

>>>

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

第二个DataFrame包含有关“纽约”和“巴塞罗那”城市的信息。

你可以使用.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

现在, 新变量all_city_data包含两个DataFrame对象中的值。

注意:从Pandas 0.25.0版开始, sort参数的默认值为True, 但是很快就会更改为False。最好为此参数提供一个明确的值, 以确保你的代码在不同的Pandas和Python版本中均能一致地工作。有关更多信息, 请查阅《 Pandas用户指南》。

默认情况下, concat()沿axis = 0合并。换句话说, 它追加行。你还可以通过提供参数axis = 1来使用它来追加列:

>>>

>>> city_countries = pd.DataFrame({
...     "country": ["Holland", "Japan", "Holland", "Canada", "Spain"], ...     "capital": [1, 1, 0, 0, 0]}, ...     index=["Amsterdam", "Tokyo", "Rotterdam", "Toronto", "Barcelona"]
... )
>>> cities = pd.concat([all_city_data, city_countries], axis=1, sort=False)
>>> cities
           revenue  employee_count  country  capital
Amsterdam   4200.0             5.0  Holland      1.0
Tokyo       6500.0             8.0    Japan      1.0
Toronto     8000.0             NaN   Canada      0.0
New York    7000.0             2.0      NaN      NaN
Barcelona   3400.0             2.0    Spain      0.0
Rotterdam      NaN             NaN  Holland      0.0

请注意, Pandas如何为缺失值添加NaN。如果只想合并出现在两个DataFrame对象中的城市, 则可以将join参数设置为inner:

>>>

>>> pd.concat([all_city_data, city_countries], axis=1, join="inner")
           revenue  employee_count  country  capital
Amsterdam     4200             5.0  Holland        1
Tokyo         6500             8.0    Japan        1
Toronto       8000             NaN   Canada        0
Barcelona     3400             2.0    Spain        0

虽然根据索引合并数据是最简单的方法, 但这并不是唯一的可能性。你可以使用.merge()来实现类似于SQL中的联接操作:

>>>

>>> countries = pd.DataFrame({
...     "population_millions": [17, 127, 37], ...     "continent": ["Europe", "Asia", "North America"]
... }, index= ["Holland", "Japan", "Canada"])
>>> pd.merge(cities, countries, left_on="country", right_index=True)

在这里, 你将参数left_on =“ country”传递给.merge(), 以指示你要加入的列。结果是一个更大的DataFrame, 它不仅包含城市数据, 还包含各个国家的人口和大洲:

Pandas 合并

请注意, 结果仅包含该国家/地区已知的城市, 并出现在加入的DataFrame中。

.merge()默认执行内部联接。如果要在结果中包括所有城市, 则需要提供how参数:

>>>

>>> pd.merge(
...     cities, ...     countries, ...     left_on="country", ...     right_index=True, ...     how="left"
... )

通过这种左联接, 你将看到所有城市, 包括没有国家/地区数据的城市:

Pandas 合并左联接

欢迎回来, 纽约和巴塞罗那!

可视化你的Pandas DataFrame

数据可视化是在Jupyter笔记本电脑中比在终端机中工作得更好的事情之一, 所以继续前进。如果你需要入门方面的帮助, 请查看Jupyter Notebook:简介。你还可以通过单击以下链接来访问包含本教程示例的Jupyter笔记本:

获取Jupyter Notebook:单击此处以获取Jupyter Notebook, 你将在本教程中使用Jupyter Notebook探索与Pandas的数据。

包括以下行以直接在笔记本中显示图:

>>>

>>> %matplotlib inline

Series和DataFrame对象都具有.plot()方法, 该方法是matplotlib.pyplot.plot()的包装。默认情况下, 它会创建一个折线图。可视化尼克斯整个赛季得分了多少分:

>>>

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

这显示了在2000年和2010年期间具有多个峰值和两个显着谷的线形图:

Pandas plot

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

>>>

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

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

Pandas plot

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

现在尝试更复杂的练习。 2013年, 迈阿密热火队获得了冠军。创建一个饼图, 显示他们在该季节中的胜利和失败计数。然后, 展开代码块以查看解决方案:

解决方案:显示/隐藏图

首先, 你定义一个标准, 使其仅包含2013年的热火比赛。然后, 以与上图相同的方式创建一个情节:

>>>

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

这是冠军派的样子:

Pandas plot

获胜的份额比损失的份额大得多!

有时, 数字可以说明一切, 但通常, 图表可以帮助你传达洞见。要了解有关可视化数据的更多信息, 请查看Python With Bokeh中的交互式数据可视化。

结论

在本教程中, 你学习了如何使用Pandas Python库开始探索数据集。你了解了如何访问特定的行和列以驯服甚至最大的数据集。说到驯服, 你还了解了多种准备和清除数据的技术, 它们可以指定列的数据类型, 处理缺失值等。你甚至还基于这些查询创建了查询, 汇总和图表。

现在你可以:

  • 使用Series和DataFrame对象
  • 使用.loc, .iloc和索引运算符对数据进行子集
  • 通过查询, 分组和汇总回答问题
  • 处理丢失, 无效和不一致的数据
  • 在Jupyter笔记本中可视化数据集

这次使用NBA统计信息的旅程只是在摸索你可以使用Pandas Python库所做的工作。你可以使用Pandas技巧来启动项目, 学习使用Python加速Pandas的技术, 甚至深入研究Pandas在后台的工作方式。还有许多其他功能供你发现, 因此请赶快解决这些数据集!

单击下面的链接, 可以获取在本教程中看到的所有代码示例:

获取Jupyter笔记本:单击此处以获取Jupyter笔记本, 你将在本教程中使用它来通过Pandas探索数据。

赞(0)
未经允许不得转载:srcmini » 使用Pandas和Python研究你的数据集

评论 抢沙发

评论前必须登录!