一、创作、阅读和写作
介绍
在这个微课程中,您将了解pandas的所有内容,pandas是最流行的用于数据分析的Python库。
在此过程中,您将使用实际数据完成几个实践练习。我们建议您在阅读相应教程的同时进行练习。
在本教程中,您将学习如何创建自己的数据,以及如何处理已经存在的数据。
入门
要使用pandas,通常从以下代码行开始。
import pandas as pd
创建数据
pandas有两个核心对象:DataFrame和Series。
DataFrame
DataFrame数据是一个表。它包含一个单独条目的数组,每个条目都有一个特定的值。每个条目对应一行(或记录)和一列。
例如,考虑以下简单的DataFrame:
pd.DataFrame({'Yes': [50, 21], 'No': [131, 2]})
在本例中,“0,No”项的值为131,“0,Yes”项的值为50,依此类推。
数据帧条目不限于整数。例如,下面是一个值为字符串的数据帧:
pd.DataFrame({'Bob': ['I liked it.', 'It was awful.'], 'Sue': ['Pretty good.', 'Bland.']})
我们使用pd.DataFrame()构造函数来生成这些DataFrame对象。声明新名称的语法是一个字典,其键是列名(本例中为Bob和Sue),其值是条目列表。这是构造新数据帧的标准方法,也是您最可能遇到的方法。
dictionary list构造函数将值分配给列标签,但只对行标签使用从0(0、1、2、3,…)开始的升序计数。有时这是可以的,但往往我们会想自己分配这些标签。
数据帧中使用的行标签列表称为索引。我们可以使用构造函数中的索引参数为其赋值:
pd.DataFrame({'Bob': ['I liked it.', 'It was awful.'],
'Sue': ['Pretty good.', 'Bland.']},
index=['Product A', 'Product B'])
系列
相反,一个系列是一系列的数据值。如果数据帧是一个表,那么序列就是一个列表。事实上,你只需要一个列表就可以创建一个:
pd.Series([1, 2, 3, 4, 5])
0 1
1 2
2 3
3 4
4 5
dtype: int64
在本质上,序列是数据帧的一列。因此,可以使用索引参数,以与以前相同的方式为序列指定列值。但是,序列没有列名,它只有一个全名:
pd.Series([30, 35, 40], index=['2015 Sales', '2016 Sales', '2017 Sales'], name='Product A')
2015 Sales 30
2016 Sales 35
2017 Sales 40
Name: Product A, dtype: int64
序列和数据帧密切相关。把一个数据帧想象成一堆“粘在一起”的序列是很有帮助的。我们将在本教程的下一节中看到更多内容。
读取数据文件
能够手动创建数据帧或序列非常方便。但是,大多数情况下,我们实际上不会手动创建自己的数据。相反,我们将处理已经存在的数据。
数据可以以多种不同的形式和格式存储。到目前为止,最基本的是简陋的CSV文件。当您打开一个CSV文件时,您会看到如下内容:
Product A,Product B,Product C,
30,21,9,
35,34,1,
41,11,11
因此,CSV文件是一个由逗号分隔的值表。因此得名:“逗号分隔值”或CSV。
现在让我们把我们的玩具数据集放在一边,看看真正的数据集在读入数据帧时是什么样子。我们将使用pd.read\u csv()函数将数据读入数据帧。所以说:
wine_reviews = pd.read_csv("../input/wine-reviews/winemag-data-130k-v2.csv")
我们可以使用shape属性来检查生成的数据帧有多大:
wine_reviews.shape
(129971, 14)
所以我们的新数据帧有13万条记录被分成14个不同的列。差不多有200万个条目!
我们可以使用head()命令检查结果数据帧的内容,该命令获取前五行:
wine_reviews.head()
pd.read_csv()函数是一个很好的函数,有30多个可选参数可以指定。例如,您可以在这个数据集中看到CSV文件有一个内置索引,pandas不会自动获取该索引。要使panda将该列用作索引(而不是从头开始创建一个新列),我们可以指定一个索引列。
wine_reviews = pd.read_csv("../input/wine-reviews/winemag-data-130k-v2.csv", index_col=0)
wine_reviews.head()
二、索引、选择和分配
介绍
选择要处理的数据帧或序列的特定值几乎是您将要运行的任何数据操作中的一个隐含步骤,因此在Python中使用数据时首先需要学习的是如何快速有效地选择与您相关的数据点。
本机访问器
原生Python对象提供了索引数据的好方法。熊猫带着所有这些,这有助于使它很容易开始。
考虑以下数据帧:
reviews
在Python中,我们可以通过将对象作为属性来访问它的属性。例如,book对象可能有title属性,我们可以通过调用book.title来访问该属性。数据框架中的列的工作方式大致相同。
因此,要访问评论的国家属性,我们可以使用:
reviews.country
如果我们有一个Python字典,我们可以使用索引([])操作符访问它的值。我们可以对数据帧中的列执行相同的操作:
reviews['country']
这是从数据帧中选择特定序列的两种方法。两者在语法上都不比另一个有效,但索引运算符[]的优点是它可以处理带有保留字符的列名(例如,如果我们有country providence列,reviews.country providence将不起作用)。
熊猫系列不是看起来像一本华丽的字典吗?基本上是这样,所以要深入到单个特定值,我们只需再次使用索引运算符[]就不足为奇了:
reviews['country'][0]
pandas索引
索引操作符和属性选择很好,因为它们的工作方式与Python生态系统的其他部分一样。作为一个新手,这使他们容易拿起和使用。然而,pandas有自己的访问器操作符loc和iloc。对于更高级的操作,这些是您应该使用的。
基于索引的选择
熊猫索引有两种模式。第一种是基于索引的选择:根据数据在数据中的数字位置选择数据。iloc遵循这一范式。
要选择数据帧中的第一行数据,可以使用以下方法:
reviews.iloc[0]
loc和iloc都是第一行,第二列。这与我们在本机Python中所做的相反,即列第一,行第二。
这意味着检索行稍微容易一些,获取检索列稍微困难一些。要使用iloc获取列,可以执行以下操作:
reviews.iloc[:, 0]
它本身也来自本机Python的:操作符,意味着“一切”。但是,当与其他选择器组合时,它可以用于指示一系列值。例如,要从第一、第二和第三行中选择国家/地区列,我们将执行以下操作:
reviews.iloc[:3, 0]
或者,只选择第二个和第三个条目,我们将执行以下操作:
reviews.iloc[1:3, 0]
也可以传递列表:
reviews.iloc[[0, 1, 2], 0]
最后,值得一提的是,负数可以用于选择。这将从值的末尾开始向前计数。例如,这里是数据集的最后五个元素。
reviews.iloc[-5:]
基于标签的选择
属性选择的第二个范例是loc操作符:基于标签的选择。在这个范例中,重要的是数据索引值,而不是它的位置。
例如,要获得评论中的第一个条目,我们现在将执行以下操作:
reviews.loc[0,'国家']
iloc在概念上比loc简单,因为它忽略了数据集的索引。当我们使用iloc时,我们将数据集视为一个大矩阵(列表列表),一个我们必须按位置索引的矩阵。相比之下,loc使用索引中的信息来完成它的工作。因为您的数据集通常有有意义的索引,所以使用loc通常更容易。例如,这里有一个使用loc更容易的操作:
reviews.loc[:, ['taster_name', 'taster_twitter_handle', 'points']]
在loc和iloc之间选择
在loc和iloc之间选择或转换时,有一个“问题”值得记住,那就是这两种方法使用的索引方案略有不同。
iloc使用Python stdlib索引方案,其中包含范围的第一个元素,排除最后一个元素。所以0:10将选择条目0,…,9.loc,同时包括索引。所以0:10将选择条目0,…,10。
为什么要改变?记住loc可以索引任何stdlib类型:例如字符串。如果我们有一个带有索引值Apples,…,Potatoes,…,并且我们想要选择“苹果和土豆之间所有按字母顺序排列的水果选择”,那么索引df.loc['Apples':'Potatoes']要比索引df.loc['Apples','Potatoet']这样的东西方便得多(字母表中的s后面是t)。
当DataFrame索引是一个简单的数字列表(例如0,…,1000)时,这尤其令人困惑。在这种情况下,df.iloc[0:1000]将返回1000个条目,而df.loc[0:1000]将返回1001个条目!要使用loc获得1000个元素,您需要降低一个并请求df.loc[0:999]。
否则,使用loc的语义与iloc相同。
操纵索引
基于标签的选择源于索引中的标签。关键的是,我们使用的索引不是不变的。我们可以用任何我们认为合适的方式操纵索引。
set_index()方法可用于执行此任务。下面是我们设置标题字段的索引时发生的情况:
reviews.set_index("title")
如果您可以为数据集创建一个比当前索引更好的索引,那么这将非常有用。
条件选择
到目前为止,我们已经使用数据帧本身的结构属性对数据的各个步骤进行了索引。然而,要对数据做一些有趣的事情,我们通常需要根据条件提出问题。
例如,假设我们对意大利生产的比普通葡萄酒更感兴趣。
我们可以从检查每种葡萄酒是否是意大利葡萄酒开始:
reviews.country == 'Italy'
这个操作根据每个记录的国家产生了一系列真/假布尔值。该结果可在loc内部用于选择相关数据:
reviews.loc[reviews.country == 'Italy']
这个数据帧有大约20000行。最初的葡萄酒大约有13万种,这意味着大约15%的葡萄酒来自意大利。
我们还想知道哪些比平均水平好。葡萄酒的评价标准是80到100分,所以这可能意味着葡萄酒至少要得到90分。
我们可以用符号(&)把这两个问题放在一起:
reviews.loc[(reviews.country == 'Italy') & (reviews.points >= 90)]
假设我们要买任何产自意大利或评级高于平均水平的葡萄酒。为此,我们使用管道(|):
reviews.loc[(reviews.country == 'Italy') | (reviews.points >= 90)]
Pandas附带了几个内置的条件选择器,我们将在这里重点介绍其中的两个。
第一个是伊辛。isin允许您选择值“在”值列表中的数据。例如,我们可以使用它来选择仅来自意大利或法国的葡萄酒:
reviews.loc[reviews.country.isin(['Italy', 'France'])]
第二个是isnull(和它的同伴notnull)。通过这些方法,可以突出显示(或不)为空(NaN)的值。例如,要过滤掉数据集中缺少价格标签的葡萄酒,我们可以这样做:
reviews.loc[reviews.price.notnull()]
分配数据
另一方面,将数据分配给数据帧很容易。您可以指定一个常量值:
reviews['critic'] = 'everyone'
reviews['critic']
或者用一个可数的值:
三、汇总函数和映射
在上一个教程中,我们学习了如何从数据帧或序列中选择相关数据。从数据表示中提取正确的数据对于完成工作至关重要,正如我们在练习中所演示的那样。
然而,数据并不总是以我们想要的格式从内存中出来。有时,我们必须自己做更多的工作,为手头的任务重新制定计划。本教程将介绍不同的操作,我们可以应用到我们的数据,以获得输入“恰到好处”。
要开始此主题的练习,请单击此处。
我们将使用葡萄酒杂志的数据进行演示。
import pandas as pd
pd.set_option('max_rows', 5)
import numpy as np
reviews = pd.read_csv("../input/wine-reviews/winemag-data-130k-v2.csv", index_col=0)
reviews
摘要函数
Pandas提供了许多简单的“摘要函数”(不是官方名称),它们以某种有用的方式重构数据。例如,考虑描述()方法:
reviews.points.describe()
此方法生成给定列的属性的高级摘要。它是类型感知的,这意味着它的输出根据输入的数据类型而改变。上述输出仅对数值数据有意义;对于字符串数据,我们得到:
reviews.taster_name.describe()
如果您想获得关于数据帧或序列中某个列的一些特定的简单摘要统计信息,通常有一个有用的函数来实现它。
例如,要查看分配的点数的平均值(例如,平均等级的葡萄酒有多好),我们可以使用mean()函数
reviews.points.mean()
要查看唯一值的列表,可以使用unique()函数:
reviews.taster_name.unique()
要查看唯一值的列表以及它们在数据集中出现的频率,我们可以使用value\u counts()方法:
reviews.taster_name.value_counts()
MAPS
映射是从数学中借用的一个术语,用于表示一个函数,它接受一组值并将它们“映射”到另一组值。在数据科学中,我们经常需要从现有数据中创建新的表示,或者将数据从现在的格式转换为我们希望它以后的格式。地图是处理这项工作的工具,它对完成工作非常重要!
您将经常使用两种映射方法。
map()是第一个,稍微简单一点。例如,假设我们想将收到的葡萄酒的分数重新计算为0。我们可以按如下方式进行:
review_points_mean = reviews.points.mean()
reviews.points.map(lambda p: p - review_points_mean)
传递给map()的函数应该期望从序列中得到一个值(在上例中是一个点值),并返回该值的转换版本。map()返回一个新的序列,其中所有值都已被函数转换。
如果我们想通过对每行调用一个自定义方法来转换整个数据帧,那么apply()就是一个等价的方法。
def remean_points(row):
row.points = row.points - review_points_mean
return row
reviews.apply(remean_points, axis='columns')
如果我们调用了reviews.apply(),axis='index',那么我们需要给出一个函数来转换每一列,而不是传递一个函数来转换每一行。
请注意,map()和apply()分别返回新的、已转换的序列和数据帧。他们不会修改他们被调用的原始数据。如果我们看第一行的评论,我们可以看到它仍然有其原始的价值。
reviews.head(1)
Pandas提供了许多常见的内置映射操作。例如,这里有一个更快的方法来重新定义我们的points列:
review_points_mean = reviews.points.mean()
reviews.points - review_points_mean
在这段代码中,我们在左侧的许多值(系列中的所有值)和右侧的单个值(平均值)之间执行一个操作。Pandas研究了这个表达式,发现我们必须从数据集中的每个值中减去这个平均值。
熊猫也会明白,如果我们在一系列等长的序列之间执行这些操作,该怎么办。例如,在数据集中结合国家和地区信息的一种简单方法是:
reviews.country + " - " + reviews.region_1
这些操作符比map()或apply()更快,因为它们使用内置的加速。所有标准Python操作符(>、<、==,等等)都是这样工作的。
但是,它们不像map()或apply()那样灵活,后者可以做更高级的事情,比如应用条件逻辑,这不能仅用加法和减法来完成。
四、分组和排序
介绍
映射允许我们将数据帧或序列中的数据转换为整列的一次一个值。但是,我们通常希望对数据进行分组,然后针对数据所在的组执行特定的操作。
正如您将了解到的,我们通过groupby()操作来实现这一点。我们还将讨论一些附加的主题,例如索引数据帧的更复杂的方法,以及如何对数据进行排序。
要开始此主题的练习,请单击此处。
分组分析
到目前为止,我们一直在大量使用的一个函数是value\u counts()函数。我们可以通过执行以下操作来复制value\u counts()的功能:
import pandas as pd
reviews = pd.read_csv("../input/wine-reviews/winemag-data-130k-v2.csv", index_col=0)
pd.set_option("display.max_rows", 5)
reviews.groupby('points').points.count()
groupby()创建了一组评论,将相同的点值分配给给定的葡萄酒。然后,对于每个组,我们抓住points()列并计算它出现的次数。value\u counts()只是此groupby()操作的快捷方式。
我们可以将以前使用过的任何摘要函数用于此数据。例如,要获得每个点值类别中最便宜的葡萄酒,我们可以执行以下操作:
reviews.groupby('points').price.min()
您可以将我们生成的每个组视为数据帧的一部分,其中只包含值匹配的数据。我们可以直接使用apply()方法访问这个数据帧,然后我们可以以任何我们认为合适的方式操作数据。例如,这里有一种方法可以从数据集中的每个酒庄中选择第一个被审查的葡萄酒的名称:
reviews.groupby('winery').apply(lambda df: df.title.iloc[0])
对于更细粒度的控件,还可以按多个列进行分组。举个例子,下面是我们如何按国家和省份挑选最好的葡萄酒:
reviews.groupby(['country', 'province']).apply(lambda df: df.loc[df.points.idxmax()])
另一个值得一提的groupby()方法是agg(),它允许您在数据帧上同时运行一组不同的函数。例如,我们可以生成数据集的简单统计摘要,如下所示:
reviews.groupby(['country']).price.agg([len, min, max])
有效地使用groupby()将允许您对数据集执行许多非常强大的操作。
多索引
到目前为止,在我们看到的所有示例中,我们都在使用带有单个标签索引的DataFrame或Series对象。groupby()稍有不同,因为根据我们运行的操作,它有时会导致所谓的多索引。
多索引与常规索引的不同之处在于它有多个级别。例如:
countries_reviewed = reviews.groupby(['country', 'province']).description.agg([len])
countries_reviewed
mi = countries_reviewed.index
type(mi)
多层次索引有几种处理层次结构的方法,而单层次索引没有这种方法。它们还需要两个级别的标签来检索值。对熊猫新手来说,处理多索引输出是一个常见的“难题”。
在pandas文档的MultiIndex/Advanced Selection部分中,详细介绍了多索引的用例以及使用它们的说明。
但是,通常,您最常用的多索引方法是用于转换回常规索引的方法,即reset\u index()方法:
countries_reviewed.reset_index()
排序
再看一下所审查的国家,我们可以看到分组按索引顺序返回数据,而不是按值顺序。也就是说,当输出groupby的结果时,行的顺序取决于索引中的值,而不是数据中的值。
要按所需顺序获取数据,我们可以自己排序。sort\u values()方法非常方便。
countries_reviewed = countries_reviewed.reset_index()
countries_reviewed.sort_values(by='len')
sort\u values()默认为升序排序,其中最低值优先。然而,大多数情况下,我们需要的是降序排序,其中较高的数字首先出现。所以说:
countries_reviewed.sort_values(by='len', ascending=False)
要按索引值排序,请使用伴随方法sort_index()。此方法具有相同的参数和默认顺序:
countries_reviewed.sort_index()
最后,要知道一次可以按多个列进行排序:
countries_reviewed.sort_values(by=['country', 'len'])
五、数据类型和缺失值
介绍
在本教程中,您将学习如何调查数据帧或序列中的数据类型。您还将学习如何查找和替换条目。
要开始此主题的练习,请单击此处。
数据类型
数据帧或序列中列的数据类型称为数据类型。
可以使用dtype属性获取特定列的类型。例如,我们可以在reviews数据框中获取price列的数据类型:
import pandas as pd
reviews = pd.read_csv("../input/wine-reviews/winemag-data-130k-v2.csv", index_col=0)
pd.set_option('max_rows', 5)
reviews.price.dtype
或者,dtypes属性返回数据帧中每列的数据类型:
reviews.dtypes
数据类型告诉我们熊猫是如何在内部存储数据的。float64表示它使用的是64位浮点数;int64表示大小类似的整数,依此类推。
要记住的一个特点(这里非常清楚地显示)是完全由字符串组成的列没有自己的类型;它们被赋予了对象类型。
可以使用astype()函数将一种类型的列转换为另一种类型,只要这种转换有意义。例如,我们可以将points列从其现有的int64数据类型转换为float64数据类型:
reviews.points.astype('float64')
数据帧或序列索引也有自己的类型:
reviews.index.dtype
Pandas还支持更奇特的数据类型,如分类数据和timeseries数据。因为这些数据类型很少使用,所以在本教程稍后的部分中我们将省略它们。
丢失的数据
缺少值的条目被赋予值NaN,即“nota Number”的缩写。出于技术原因,这些NaN值始终为float64数据类型。
Pandas提供了一些特定于丢失数据的方法。要选择NaN条目,可以使用pd.isnull()(或它的同伴pd.notnull())。这意味着要这样使用:
reviews[pd.isnull(reviews.country)]
替换缺少的值是一种常见的操作。Pandas为这个问题提供了一个非常方便的方法:fillna()。fillna()提供了一些不同的策略来缓解这些数据。例如,我们可以简单地将每个NaN替换为“未知”:
reviews.region_2.fillna("Unknown")
或者,我们可以用数据库中给定记录之后出现的第一个非空值来填充每个缺少的值。这就是所谓的回填策略。
或者,我们可能有一个非空值,我们想取代。例如,假设自从这个数据集发布以来,评论家Kerin O'Keefe已经将她的Twitter句柄从@kerinokeefe更改为@kerino。在数据集中反映这一点的一种方法是使用replace()方法:
reviews.taster_twitter_handle.replace("@kerinokeefe", "@kerino")
replace()方法在这里值得一提,因为它可以方便地替换丢失的数据,这些数据在数据集中被赋予某种前哨值:“未知”、“未公开”、“无效”等等。
6、重命名和合并
介绍
通常,数据会以列名、索引名或其他我们不满意的命名约定出现。在这种情况下,您将学习如何使用pandas函数将违规条目的名称更改为更好的名称。
您还将探索如何组合来自多个数据帧和/或序列的数据。
要开始此主题的练习,请单击此处。
重命名
我们将在这里介绍的第一个函数是rename(),它允许您更改索引名和/或列名。例如,要将数据集中的points列更改为score,我们将执行以下操作:
import pandas as pd
pd.set_option('max_rows', 5)
reviews = pd.read_csv("../input/wine-reviews/winemag-data-130k-v2.csv", index_col=0)
reviews.rename(columns={'points': 'score'})
rename()通过分别指定index或column关键字参数,可以重命名索引或列值。它支持多种输入格式,但通常Python字典是最方便的。下面是一个使用它重命名索引中某些元素的示例。
reviews.rename(index={0: 'firstEntry', 1: 'secondEntry'})
您可能会经常重命名列,但很少重命名索引值。为此,设置索引()通常更方便。
行索引和列索引都可以有自己的name属性。方法可以用来更改这些名称。例如:
reviews.rename_axis("wines", axis='rows').rename_axis("fields", axis='columns')
结合
在对数据集执行操作时,我们有时需要以非平凡的方式组合不同的数据帧和/或序列。熊猫有三个核心方法来做这件事。按照复杂性的增加顺序,依次是concat()、join()和merge()。merge()所能做的大部分工作也可以通过join()更简单地完成,因此我们将省略它,并将重点放在前两个函数上。
最简单的组合方法是concat()。给定一个元素列表,此函数将沿着一个轴将这些元素合并在一起。
当数据位于不同的DataFrame或Series对象中但具有相同的字段(列)时,这非常有用。一个例子是YouTube视频数据集,它根据来源国(例如本例中的加拿大和英国)对数据进行拆分。如果我们想同时研究多个国家,我们可以使用concat()将它们组合在一起:
canadian_youtube = pd.read_csv("../input/youtube-new/CAvideos.csv")
british_youtube = pd.read_csv("../input/youtube-new/GBvideos.csv")
pd.concat([canadian_youtube, british_youtube])
就复杂性而言,最中间的组合器是join()。join()允许您组合具有共同索引的不同数据帧对象。例如,要调出加拿大和英国在同一天流行的视频,我们可以执行以下操作:
left = canadian_youtube.set_index(['title', 'trending_date'])
right = british_youtube.set_index(['title', 'trending_date'])
left.join(right, lsuffix='_CAN', rsuffix='_UK')
suffix和rsuffix参数在这里是必需的,因为数据在英国和加拿大的数据集中具有相同的列名。如果这不是真的(因为,比方说,我们事先已经重新命名了它们),我们就不需要它们了。