Python-数据分析工具包-全-

Python 数据分析工具包(全)

原文:A Python Data Analyst’s Toolkit

协议:CC BY-NC-SA 4.0

一、熟悉 Python

Python 是一种开源编程语言,由荷兰程序员吉多·范·罗苏姆创建。Python 以英国喜剧团体 Monty Python 命名,是一种高级、解释型、开源语言,是当今世界上最受欢迎、发展最快的编程语言之一。它也是数据科学和机器学习的首选语言。

在这一章中,我们首先介绍 Jupyter notebook——一个用 Python 运行代码的 web 应用程序。然后,我们将介绍 Python 中的基本概念,包括数据类型、操作符、容器、函数、类和文件处理、异常处理,以及编写代码和模块的标准。

本书的代码示例是使用 Python 版本 3.7.3 和 Anaconda 版本 4.7.10 编写的。

技术要求

Anaconda 是一个开源平台,被 Python 程序员和数据科学家广泛使用。安装这个平台会安装 Python、Jupyter 笔记本应用程序和数百个库。下面是安装 Anaconda 发行版需要遵循的步骤。

img/498042_1_En_1_Fig1_HTML.jpg

图 1-1

安装 Anaconda

  1. 打开以下网址: https://www.anaconda.com/products/individual

  2. 点击操作系统的安装程序,如图 1-1 所示。安装程序会下载到您的系统中。

img/498042_1_En_1_Fig2_HTML.jpg

图 1-2

发射 Jupyter

  1. 打开安装程序(上一步下载的文件)并运行它。

  2. 安装完成后,在开始菜单旁边的资源管理器(搜索栏)中键入“jupyter notebook”或“jupyter”打开 Jupyter 应用程序,如图 1-2 (针对 Windows OS 显示)。

请按照以下步骤下载本书中使用的所有数据文件:

  • 点击以下链接: https://github.com/DataRepo2019/Data-files

  • 选择绿色的“代码”菜单,并从该菜单的下拉列表中单击“下载 ZIP”

  • 从下载的 zip 文件夹中提取文件,并将这些文件导入到 Jupyter 应用程序中

现在我们已经安装并启动了 Jupyter,让我们在下一节了解如何使用这个应用程序。

开始使用 jupiter 笔记本

在讨论 Jupyter 笔记本的本质之前,让我们先来讨论一下什么是集成开发环境(IDE)。IDE 集合了编程中涉及的各种活动,包括编写和编辑代码、调试和创建可执行文件。它还包括自动完成(完成用户想要键入的内容,从而使用户能够专注于逻辑和解决问题)和语法突出显示(突出显示语言的各种元素和关键字)等功能。除了 Jupyter,还有很多 Python 的 ide,包括 Enthought Canopy、Spyder、PyCharm 和 Rodeo。Jupyter 成为数据科学社区中无处不在、事实上的标准有几个原因。这些优势包括易于使用和定制、支持多种编程语言、平台独立性、方便访问远程数据,以及将输出、代码和多媒体结合在一个屋檐下的好处。

JupyterLab 是 Jupyter 笔记本的 IDE。Jupyter 笔记本是在用户机器上本地运行的网络应用程序。它们可用于加载、清理、分析和建模数据。您可以在 Jupyter 笔记本中添加代码、公式、图像和降价文本。Jupyter 笔记本有两个用途,一是运行您的代码,二是作为一个平台,展示您的工作并与他人分享。让我们看看这个应用程序的各种特性。

img/498042_1_En_1_Fig4_HTML.jpg

图 1-4

Jupyter 单元格中的简单代码语句

  1. 输入并执行代码

    在笔记本的第一个单元格内点击,输入一行简单的代码,如图 1-4 所示。从“单元格”菜单中选择运行单元格来执行代码,或者使用快捷键 Ctrl+Enter

img/498042_1_En_1_Fig3_HTML.jpg

图 1-3

创建新的 Jupyter 笔记本

  1. 打开仪表板

    在“开始”菜单旁边的搜索栏中键入“jupyter notebook”。这将打开 Jupyter 仪表板。仪表板可用于创建新笔记本或打开现有笔记本。

  2. 创建新笔记本

    在 Jupyter 仪表盘右上角选择 New 创建一个新的 Jupyter 笔记本,然后在出现的下拉列表中选择 Python 3 ,如图 1-3 所示。

img/498042_1_En_1_Fig5_HTML.jpg

图 1-5

将模式更改为降价

  1. 添加降价文本或标题

    在新的单元格中,通过选择如图 1-5 所示的 Markdown 或者按键盘上的 Esc+M 键来改变格式。您也可以通过从如下所示的下拉列表中选择标题或按快捷键 Esc+(1/2/3/4)来为 Jupyter 笔记本添加标题。

img/498042_1_En_1_Fig6_HTML.jpg

图 1-6

更改文件的名称

  1. 重命名笔记本

    点击笔记本默认名称,输入新名称,如图 1-6 所示。

    您也可以通过选择文件➤重命名来重命名笔记本。

img/498042_1_En_1_Fig7_HTML.jpg

图 1-7

下载 Jupyter 笔记本

  1. 保存笔记本

    按 Ctrl+S 或选择文件➤保存和检查点。

  2. 下载笔记本

    您可以通过电子邮件或共享您的笔记本,方法是使用选项文件➤下载为➤笔记本(。ipynb) ,如图 1-7 所示。

jupiter 中的快捷方式和其他功能

让我们来看看 Jupyter 笔记本的一些关键特性,包括快捷键、制表符补全和魔法命令。

表 1-1 给出了一些在 Jupyter 笔记本中常见的图标、相应的菜单功能和键盘快捷键。

表 1-1

Jupyter 笔记本工具栏功能

|

工具栏中的图标

|

功能

|

键盘快捷键

|

菜单功能

|
| --- | --- | --- | --- |
| img/498042_1_En_1_Figa_HTML.jpg | 保存 Jupyter 笔记本 | Esc+s | 文件➤另存为 |
| img/498042_1_En_1_Figb_HTML.jpg | 向 Jupyter 笔记本添加新单元格 | Esc+b (在当前单元格下方增加一个单元格),或者 Esc+a (在当前单元格上方增加一个单元格) | 在上方插入➤插入单元格或在下方插入➤插入单元格 |
| img/498042_1_En_1_Figc_HTML.jpg | 剪切选定的单元格 | Esc+x | 编辑➤剪切单元格 |
| img/498042_1_En_1_Figd_HTML.jpg | 复制选定的单元格 | Esc+c | 编辑➤复制单元格 |
| img/498042_1_En_1_Fige_HTML.jpg | 将一个单元格粘贴到另一个选定单元格的上方或下方 | Esc+v | 编辑上方的➤粘贴单元格或编辑下方的➤粘贴单元格 |
| img/498042_1_En_1_Figf_HTML.jpg | 运行给定的单元 | Ctrl+Enter (运行选中的单元格); Shift+Enter (运行选中单元格并插入新单元格) | 细胞➤运行选定的细胞 |
| img/498042_1_En_1_Figg_HTML.jpg | 中断内核 | Esc+ii | 内核➤中断 |
| img/498042_1_En_1_Figh_HTML.jpg | 重启内核 | Esc+00 | 内核➤重启 |

如果您不确定使用哪个快捷键,请进入:帮助➤快捷键,如图 1-8 所示。

img/498042_1_En_1_Fig8_HTML.jpg

图 1-8

Jupyter 中的帮助菜单

常用的键盘快捷键包括

  • Shift+Enter 运行当前单元格中的代码,移动到下一个单元格。

  • Esc 离开单元格。

  • Esc+M 将单元格的模式更改为“降价”模式。

  • Esc+Y 将单元格的模式改为“编码”。

制表符结束

这是一个可以用在 Jupyter 笔记本上的功能,帮助你完成正在编写的代码。使用制表符补全可以加快工作流程,减少错误,并快速补全函数名,从而减少打字错误,使您不必记住所有模块和函数的名称。

例如,如果您想要导入 Matplotlib 库,但不记得拼写,您可以键入前三个字母 mat,然后按 Tab。您会看到一个下拉列表,如图 1-9 所示。库的正确名称是下拉列表中的第二个名称。

img/498042_1_En_1_Fig9_HTML.jpg

图 1-9

Jupyter 中的制表符结束

木星中使用的魔法命令

魔术命令是以一个或多个%符号开始的特殊命令,后面跟一个命令。以一个%符号开始的命令适用于单行代码,以两个%符号开始的命令适用于整个单元(单元内的所有代码行)。

一个常用的神奇命令,如下所示,用于在笔记本中显示 Matplotlib 图形。添加这个神奇的命令避免了为显示图形而单独调用 plt.show 函数的需要(Matplotlib 库在第七章中详细讨论)。

代码:

%matplotlib inline

神奇的命令,如 timeit ,也可以用来计时脚本的执行,如下所示。

代码:

%%timeit
for i in range(100000):
    i*i

输出:

16.1 ms ± 283 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)

既然您已经了解了使用 Jupyter 笔记本的基本知识,那么让我们开始学习 Python 并了解这种语言的核心方面。

Python 基础

在本节中,我们将熟悉 Python 的语法、注释、条件语句、循环和函数。

注释、打印和输入

在这一节中,我们将介绍一些基础知识,比如打印、从用户那里获得输入,以及添加注释来帮助他人理解您的代码。

评论

注释解释一行代码做什么,程序员用它来帮助其他人理解他们写的代码。在 Python 中,注释以#符号开始。

适当的间距和缩进在 Python 中至关重要。Java 和 C++等其他语言使用括号来括住代码块,而 Python 使用缩进四个空格来指定代码块。人们需要注意缩进以避免错误。像 Jupyter 这样的应用程序通常会注意缩进,并自动在代码块的开头添加四个空格。

印刷

打印功能将内容打印到屏幕或任何其他输出设备。

通常,我们将字符串和变量的组合作为参数传递给 print 函数。参数是包含在函数括号内的值,函数用它来产生结果。在下面的语句中,“你好!”是 print 函数的参数。

代码:

print("Hello!")

为了打印多行代码,我们在字符串的开头和结尾使用三重引号,例如:

代码:

print('''Today is a lovely day.
It will be warm and sunny.
It is ideal for hiking.''')

输出:

Today is a lovely day.
It will be warm and sunny.
It is ideal for hiking.

注意,在 Python 中我们不像其他语言那样使用分号来结束语句。

格式方法可以与打印方法结合使用,用于在字符串中嵌入变量。它使用花括号作为变量的占位符,这些变量作为参数传递给方法。

让我们看一个简单的例子,我们使用格式方法打印变量。

代码:

weight=4.5
name="Simi"
print("The weight of {} is {}".format(name,weight))

输出:

The weight of Simi is 4.5

在没有 format 方法的情况下,上述语句也可以重写如下:

代码:

print("The weight of",name,"is","weight")

请注意,只有 print 参数的字符串部分用引号括起来。变量名不在引号内。同样,如果在打印参数中有任何常量,它们也不会出现在引号中。在下面的示例中,一个布尔常量(True)、一个整数常量(1)和字符串组合在一个 print 语句中。

代码:

print("The integer equivalent of",True,"is",1)

输出:

The integer equivalent of True is 1

格式字段可以指定浮点数的精度。浮点数是带小数点的数字,小数点后的位数可以使用如下格式字段指定。

代码:

x=91.234566
print("The value of x upto 3 decimal points is {:.3f}".format(x))

输出:

The value of x upto 3 decimal points is 91.235

我们可以指定传递给方法的变量的位置。在这个例子中,我们使用位置“1”来引用参数列表中的第二个对象,使用位置“0”来指定参数列表中的第一个对象。

代码:

y='Jack'
x='Jill'
print("{1} and {0} went up the hill to fetch a pail of water".format(x,y))

输出:

Jack and Jill went up the hill to fetch a pail of water

投入

输入功能接受用户的输入。用户提供的输入被存储为类型为字符串的变量。如果您想对任何数字输入进行数学计算,您需要将输入的数据类型更改为 int 或 float,如下所示。

代码:

age=input("Enter your age:")
print("In 2010, you were",int(age)-10,"years old")

输出:

Enter your age:76
In 2010, you were 66 years old

Python 中输入/输出的进一步阅读: https://docs.python.org/3/tutorial/inputoutput.html

变量和常数

常量或文字是不变的值,而变量包含可以改变的值。我们不需要在 Python 中声明一个变量,也就是指定它的数据类型,不像 Java 和 C/C++等其他语言。我们通过给变量命名并赋值来定义它。根据该值,数据类型会自动分配给它。使用赋值运算符(=)将值存储在变量中。Python 中命名变量的规则如下:

  • 变量名不能有空格

  • 变量不能以数字开头

  • 变量名只能包含字母、数字和下划线(_)

  • 变量不能使用保留关键字的名称(例如,继续中断打印等)。是 Python 语言中预定义的术语,具有特殊含义,作为变量名无效)

经营者

以下是 Python 中一些常用的运算符。

算术运算符:取两个整数值或浮点值,执行一次运算,返回值。

Python 支持以下算术运算符:

  • **(指数)

  • %(模或余数),

  • //(商),

  • *(乘法)

  • -(减法)

  • +(加法)

操作顺序至关重要。括号优先于指数,指数优先于除法和乘法,乘法优先于加法和减法。设计了一个首字母缩略词——p . e . d . m . a . s .(请原谅我亲爱的萨莉阿姨)——可以用来记住这些运算的顺序,以了解算术表达式中首先需要应用哪个运算符。下面给出了一个例子:

代码:

(1+9)/2-3

输出:

2.0

在前面的表达式中,首先执行括号内的运算,得到 10,然后执行除法,得到 5,最后执行减法,得到最终输出 2。

比较操作符:这些操作符比较两个值,并评估为真或假值。Python 支持以下比较运算符:

  • :大于

  • :小于

  • < =:小于或等于

  • =:大于或等于

  • ==:平等。请注意,这不同于赋值运算符(=)

  • !=(不等于)

逻辑(或布尔)运算符:类似于比较运算符,它们也评估为值。这些运算符对布尔变量或表达式进行运算。Python 支持以下逻辑运算符:

  • and 运算符:使用该运算符的表达式只有在其所有子表达式都为时,才计算为。否则,如果它们中的任何一个为,则表达式评估为

    下面显示了一个使用操作符的例子。

    CODE:

    (2>1) and (1>3)
    
    

    Output:

    False
    
    
  • 运算符:使用了运算符的表达式,如果表达式中的任何一个子表达式为,则计算结果为。如果表达式的所有子表达式都评估为 False,则该表达式评估为 False

    下面显示了一个使用操作符的例子。

    CODE:

    (2>1) or (1>3)
    
    

    Output:

    True
    
    
  • not 运算符:使用了 not 运算符的表达式,如果表达式为 False ,则计算结果为 True ,反之亦然。

    下面是一个使用而不是运算符的例子。

    CODE:

    not(1>2)
    
    

    Output:

    True
    
    

赋值运算符

这些运算符为变量或操作数赋值。以下是 Python 中使用的赋值运算符列表:

  • =(给变量赋值)

  • +=(将右边的值与左边的操作数相加)

  • -=(从左边的操作数中减去右边的值)

  • *=(将左边的操作数乘以右边的值)

  • %=(返回左边的操作数除以右边的值后的余数)

  • /=(返回左边的操作数除以右边的值后的商)

  • //=(只返回左边的操作数除以右边的值后的商的整数部分)

下面给出了一些使用这些赋值运算符的例子。

代码:

x=5 #assigns the value 5 to the variable x
x+=1 #statement adds 1 to x (is equivalent to x=x+1)
x-=1 #statement subtracts 1 from x (is equivalent to x=x-1)
x*=2 #multiplies x by 2(is equivalent to x=x*2)
x%=3 #equivalent to x=x%3, returns remainder
x/=3 #equivalent to x=x/3, returns both integer and decimal part of quotient

x//=3 #equivalent to x=x//3, returns only the integer part of quotient after dividing x by 3

恒等运算符(is 和 not is)

这些操作符检查两个对象是否相等,即两个对象是否指向同一个值,并根据它们是否相等返回一个布尔值( True/False )。在下面的示例中,三个变量" x"、【y】"z" 包含相同的值,因此,当比较" x "和" z "时,恒等运算符()返回 True

示例:

x=3
y=x
z=y
x is z

输出:

True

隶属运算符(in 和 not in)

这些操作符检查一个特定的值是否出现在一个字符串或一个容器中(像列表和元组,在下一章讨论)。如果值存在,操作符中的返回“真”,如果值不在字符串或容器中,操作符中的返回“真”。

代码:

'a' in 'and'

输出:

True

数据类型

数据类型是变量的类别或类型,基于它存储的值。

变量或常量的数据类型可以使用 type 函数获得。

代码:

type(45.33)

输出:

float

表 1-2 中给出了一些常用的数据类型。

表 1-2

Python 中的常见数据类型

|

数据类型

|

数据类型

|

例子

|
| --- | --- | --- |
| 数字数据 | int :对于没有小数点的数字float :带小数点的数字 | #int``a=1``#float``b=2.4 |
| 顺序 | 序列存储多个值。Python 中的一些序列是:列表范围元组 | #tuple``a=(1,2,3)``#list``b=[1,2,3]``#range``c=range(5) |
| 字符或文本 | str 是用于存储引号内的单个字符或字符序列的数据类型 | #single character``X='a'``#multiple characters``x='hello world'``#multiple lines``x='''hello world``good morning''' |
| 布尔数据 | bool 是用于存储真值或假值的数据类型 | X=True``Y=False |
| 映射对象 | 字典是字典的数据类型(一个将键映射到值的对象) | x={'Apple':'fruit','Carrot':'vegetable'} |

代表日期和时间

Python 有一个名为 datetime 的模块,允许我们定义日期、时间或持续时间。

我们首先需要导入这个模块,这样我们就可以使用这个模块中可用的函数来定义一个日期或时间对象,使用下面的语句。

代码:

import datetime

让我们使用本模块中的方法来定义各种日期/时间对象。

日期对象

可以使用 date 方法定义由日、月和年组成的日期,如下所示。

代码:

date=datetime.date(year=1995,month=1,day=1)
print(date)

输出:

1995-01-01

注意, date 方法的所有三个参数——日、月和年——都是必需的。如果在定义一个日期对象时跳过这些参数中的任何一个,就会出现错误,如下所示。

代码:

date=datetime.date(month=1,day=1)
print(date)

输出:

TypeError                         Traceback (most recent call last)
<ipython-input-3-7da76b18c6db> in <module>
----> 1 date=datetime.date(month=1,day=1)
      2 print(date)

TypeError: function missing required argument 'year' (pos 1)

时间对象

为了在 Python 中定义一个存储时间的对象,我们使用了 time 方法。

可以传递给此方法的参数可能包括小时、分钟、秒或微秒。注意,与日期方法不同,参数对于时间方法不是强制性的(它们可以被跳过)。

代码:

time=datetime.time(hour=12,minute=0,second=0,microsecond=0)
print("midnight:",time)

输出:

midnight: 00:00:00

日期时间对象

我们还可以使用 datetime 方法定义一个由日期和时间组成的 datetime 对象,如下所示。对于此方法,日期参数——日、月和年——是必需的,但时间参数(如小时、分钟等)是必需的。)可以跳过。

代码:

datetime1=datetime.datetime(year=1995,month=1,day=1,hour=12,minute=0,second=0,microsecond=0)
print("1st January 1995 midnight:", datetime1)

输出:

1st January 1995 midnight: 1995-01-01 12:00:00

时三角洲对象

一个 timedelta 对象代表一个特定的持续时间,并且是使用 timedelta 方法创建的。

让我们创建一个存储 17 天时间的 timedelta 对象。

代码:

timedelta1=datetime.timedelta(weeks=2,days=3)
timedelta1

输出:

datetime.timedelta(days=17)

在创建一个 timedelta 对象时,您还可以添加其他参数,如秒、分钟和小时。

可以将 timedelta 对象添加到现有的 date 或 datetime 对象中,但不能添加到 time 对象中

将持续时间(时间增量对象)添加到日期对象:

代码:

#adding a duration to a date object is supported
date1=datetime.date(year=1995,month=1,day=1)
timedelta1=datetime.timedelta(weeks=2,days=3)
date1+timedelta1

输出:

datetime.date(1995, 1, 18)

将持续时间(时间增量对象)添加到日期时间对象:

代码:

#adding a duration to a datetime object is supported
datetime1=datetime.datetime(year=1995,month=2,day=3)
timedelta1=datetime.timedelta(weeks=2,days=3)
datetime1+timedelta1

输出:

datetime.datetime(1995, 2, 20, 0, 0)

时间对象添加持续时间会导致错误:

代码:

#adding a duration to a time object is not supported
time1=datetime.time(hour=12,minute=0,second=0,microsecond=0)
timedelta1=datetime.timedelta(weeks=2,days=3)
time1+timedelta1

输出:

TypeError                             Traceback (most recent call last)
<ipython-input-9-5aa64059a69a> in <module>
      2 time1=datetime.time(hour=12,minute=0,second=0,microsecond=0)
      3 timedelta1=datetime.timedelta(weeks=2,days=3)
----> 4 time1+timedelta1

TypeError: unsupported operand type(s) for +: 'datetime.time' and 'datetime.timedelta'

延伸阅读:

了解有关 Python 日期时间模块的详细信息

https://docs.python.org/3/library/datetime.html

使用字符串

字符串是用引号括起来的一个或多个字符的序列(单引号和双引号都可以接受)。字符串的数据类型是 str。 Python 不支持字符数据类型,不像 Java 和 c 这样的老语言。即使是单个字符,如‘a’,‘b’,也存储为字符串。字符串在内部存储为数组,并且是不可变的(不能修改)。让我们看看如何定义一个字符串。

定义字符串

单行字符串可以用单引号或双引号来定义。

代码:

x='All that glitters is not gold'
#OR
x="All that glitters is not gold"

对于多行字符串,使用三重引号:

代码:

x='''Today is Tuesday.
Tomorrow is Wednesday'''

字符串操作

字符串可以使用各种函数,下面将解释其中一些函数。

  1. 查找字符串的长度: len 函数可用于计算字符串的长度,如下所示。

    代码:

    len('Hello')
    
    

    Output:

    5
    
    
  2. 访问字符串中的单个元素:

    可以使用索引运算符[]提取字符串中的单个字符。

    CODE:

    x='Python'
    x[3]
    Output
    
    :
    'h'
    
    
  3. 对字符串进行切片:切片是指提取对象的一部分或子集(在这种情况下,对象是字符串)。切片也可以用于其他可迭代对象,比如列表和元组,我们将在下一章讨论。冒号操作符用于切片,带有可选的开始、停止和步进索引。下面提供了一些切片的例子。

    CODE:

    x='Python'
    x[1:] #from second character to the end
    
    

    输出:

    'ython'
    Some more examples of slicing:
    
    

    CODE:

    x[:2] #first two characters. The starting index is assumed to be 0
    
    

    Output:

    'Py'
    
    

    CODE:

    x[::-1]#reversing the string, the last character has an index -1
    
    

    Output:

    'nohtyP'
    
    
  4. 理由:

    为了向右侧或左侧添加空格,或者使字符串居中,使用了 rjustljustcenter 方法。传递给这种方法的第一个参数是新字符串的长度,可选的第二个参数是用于填充的字符。默认情况下,空格用于填充。

    代码:

    '123'.rjust(5,"*")
    
    

    Output:

    '**123'
    
    
  5. 改变大小写:改变字符串的大小写,使用的方法,如下图所示。

    代码:

    'COLOR'.lower()
    
    

    Output:

    'color'
    
    
  6. 检查字符串包含的内容:

    为了检查字符串是以给定的字符开始还是结束,使用了 startswithendswith 方法。

    代码:

    'weather'.startswith('w')
    
    

    Output:

    True
    
    
  7. 从字符串中删除空格:

    要删除字符串中的空格,请使用 strip 方法(删除两端的空格)、rst strip(删除右端的空格)或 lstrip 方法(删除左端的空格)。下面显示了一个示例。

    代码:

    '  Hello'.lstrip()
    
    

    Output:

    'Hello'
    
    
  8. 检查字符串的内容:

    有几种方法可以检查一个字符串包含什么,比如isalpha, isupper, isdigit, isalnum等等。只有当字符串中的所有字符都满足给定条件时,所有这些方法才返回“True”。

    代码:

    '981'.isdigit()#to check for digits
    
    

    Output:

    True
    
    

    CODE:

    'Abc'.isupper()
    #Checks if all characters are in uppercase. Since all letters are not uppercase, the condition is not satisfied
    
    

    Output:

    False
    
    
  9. 连接字符串列表:

    join 方法将一系列字符串组合成一个字符串。在 join 方法的左侧,我们提到了用于连接字符串的引号中的分隔符。在右边,我们传递单个字符串的列表。

    代码:

    ' '.join(['Python','is','easy','to','learn'])
    
    

    Output:

    'Python is easy to learn'
    
    
  10. 拆分字符串:

*split* 方法的作用与 join 方法相反。它将一个字符串分解成一个单词列表,并返回这个列表。如果我们只传递一个单词给这个方法,它会返回一个只包含一个单词的列表,并且不会进一步分割字符串。

代码:

```py
'Python is easy to learn'.split()

```

Output:

```py
['Python', 'is', 'easy', 'to', 'learn']

```

条件语句

条件语句,顾名思义,计算一个条件或一组条件。在 Python 中, if-elif-else 构造用于此目的。Python 没有 switch-case 结构,这种结构在其他一些语言中用于条件执行。

条件语句以 if 关键字和表达式或要计算的条件开始。接下来是一个代码块,仅当条件评估为“真”时才执行;否则将被跳过。

else 语句(不包含任何条件)用于在 if 语句中提到的条件不满足时执行一段代码。 elif 语句用于评估特定条件。elif 语句的顺序很重要。如果 elif 语句之一评估为真,则后面的 elif 语句根本不执行。 if 语句也可以单独存在,不提及 elseelif 语句。

以下示例演示了 if-elif-else 构造。

代码:

#if-elif-else
color=input('Enter one of the following colors - red, orange or blue:')
if color=='red':
    print('Your favorite color is red')
elif color=='orange':
    print('Your favorite color is orange')
elif color=='blue':
    print('Your favorite color is blue')
else:
    print("You entered the wrong color")

输出:

Enter one of the following colors - red, orange or blue:pink
You entered the wrong color

条件语句可以嵌套,这意味着我们可以将一个条件语句(内部)放在另一个条件语句(外部)中。使用嵌套语句时,需要特别注意缩进。下面显示了一个嵌套的 if 语句的例子。

代码:

#nested conditionals
x=20
if x<10:
    if x<5:
        print("Number less than 5")
    else:
        print("Number greater than 5")
else:
    print("Number greater than 10")

输出:

Number greater than 10

延伸阅读:查看更多关于如果声明: https://docs.python.org/3/tutorial/controlflow.html#if-statements

循环用于重复执行部分代码。代码块的一次执行称为一次迭代,循环通常会经历多轮迭代。Python 中使用了两种类型的循环——用于循环的和用于循环的

While 循环

只要条件为“真”,当我们想要执行特定指令时,就会使用 while 循环。代码块执行后,执行将返回到代码块的开头。下面显示了一个示例。

代码:

#while loop with continue statement
while True:
    x=input('Enter the correct color:')
    if(x!='red'):
        print("Color needs to be entered as red")
        continue
    else:
        break

输出:

Enter the correct color:blue
Color needs to be entered as red
Enter the correct color:yellow
Color needs to be entered as red
Enter the correct color:red

在前面的示例中,第一条语句( while True )用于执行无限循环。一旦输入的用户名长度正确,break 语句就会在循环之外执行;否则,将向用户显示一条消息,要求输入正确长度的用户名。请注意,在代码块的最后一条语句之后,执行会自动转到循环的开头。

break 语句用于将控制置于循环之外。当我们想要跳出一个无限循环时,这是很有用的。

continue 语句的作用正好相反——它控制循环的开始。关键字 breakcontinue 可以用于循环和条件语句,比如 if/else

延伸阅读:

查看有关以下内容的更多信息:

for 循环

循环的用于执行一段代码预定的次数。循环的可用于任何类型的 iterable 对象,也就是说,可由循环用于运行重复实例或迭代的值序列。这些可迭代对象包括列表、元组、字典和字符串。

回路的通常也与范围功能一起使用。range 函数创建了一个 range 对象,另一个可迭代对象,它是一个等间距整数序列。考虑下面的例子,我们使用 for 循环计算前五个奇数的总和。

代码:

#for loop
sum=0
for i in range(1,10,2):
    sum=sum+i
print(sum)

输出:

25

范围函数有三个参数:开始参数、停止参数和步进参数。这三个参数都不是强制性的。从 0 到 9(包括 0 和 9)的数字可以生成为范围(10)范围(0,10)范围(0,10,1)。默认开始参数为 0,默认步进参数为 1。

For 循环也可以嵌套(一个外循环和任意数量的内循环),如下所示。

代码:

#nested for loop
for i in 'abcd':
    for j in range(4):
        print(i,end=" ")
    print("\n")

输出:

a a a a

b b b b

c c c c

d d d d

延伸阅读:查看更多关于 for 的声明: https://docs.python.org/3/tutorial/controlflow.html#for-statements

功能

一个函数可以被认为是一个“黑盒”(用户不需要关心函数的内部工作),它接受输入,处理输入,并产生输出。函数本质上是执行特定任务的语句块。

在 Python 中,使用 def 关键字定义函数。后面是函数名和一个或多个可选参数。参数是只存在于函数中的变量。函数中定义的变量具有局部作用域,这意味着不能在函数外部访问它们。它们也被称为局部变量。外部代码或函数不能操作函数中定义的变量。

一个函数可以有一个可选的返回值。返回值是由返回到主程序的函数产生的输出。调用函数意味着给函数输入(参数)来执行任务并产生输出。

函数的效用在于它们的可重用性。它们还有助于避免冗余和将代码组织成逻辑块。我们只需要给它提供运行指令所需的一组输入。可以重复调用一个函数,而不是手动输入相同的代码行。

例如,假设您想在给定的数字列表中找出质数。一旦你写了一个函数来检查一个整数是否是一个质数,你可以简单地将列表中的每个数字作为参数传递给函数并调用它,而不是为你想要测试的每个整数写相同的代码行。

代码:

def checkPrime(i):
     #Assume the number is prime initially
    isPrime=True
    for j in range(2,i):
        # checking if the number is divisible by any number between 2 and i
        if i%j==0:
            #If it is divisible by any number in the j range, it is not prime
            isPrime=False
    # This is the same as writing if isPrime==True
    if isPrime:
        print(i ,"is prime")
    else:
        print(i, "is not prime")
for i in range(10,20):
    checkPrime(i)

输出:

10 is not prime
11 is prime
12 is not prime
13 is prime
14 is not prime
15 is not prime
16 is not prime
17 is prime
18 is not prime
19 is prime

延伸阅读:查看更多关于定义函数: https://docs.python.org/3/tutorial/controlflow.html#defining-functions

匿名或λ****函数使用λ关键字定义。它们是单表达式函数,提供了一种简洁的方法来定义函数,而无需将函数对象绑定到名称。这些函数被称为“匿名”的原因是它们不需要名字。考虑下面的例子,我们使用 lambda 函数来计算两个数的和。

代码:

(lambda x,y:(x+y))(5,4)

输出:

9

注意匿名函数的语法。它以关键字 lambda 开始,后面是参数(在本例中是‘x’和‘y’)。然后是冒号,冒号后是一个表达式,它被求值并返回。没有必要提到 return 语句,因为在这样的函数中有一个隐式的 return。请注意,该函数也没有名称。

语法错误和异常

语法错误是用户在编写代码时可能无意中犯下的错误,例如,拼写错误的关键字、没有缩进代码等等。另一方面,异常是程序执行过程中出现的错误。用户在运行程序时可能会输入不正确的数据。如果你想用一个数(比如说‘a’)除以另一个数(比如说‘b’),但是给分母(比如说‘b’)一个 0 的值,这将会产生一个异常。Python 中自动生成并显示给用户的异常可能无法清楚地表达问题。使用带有 try-except 构造的异常处理,我们可以构造一个用户友好的消息,使用户能够更好地纠正错误。

异常处理有两个部分。首先,我们将可能导致错误的代码放在一个 try 子句下。然后,在 except 子句中,我们尝试处理在 try 块中导致错误的任何原因。我们在 except 子句中提到异常类的名称,后面是我们处理错误的代码块。处理错误的一个简单方法是打印一条消息,告诉用户需要纠正的更多细节。

注意,所有异常都是从类 BaseException 派生的对象,并且遵循层次结构。

延伸阅读:Python 中异常的类层次结构可以在这里找到: https://docs.python.org/3/library/exceptions.html#exception-hierarchy

下面是一个简单的程序例子,有和没有异常处理。

while True:
    try:
        n=int(input('Enter your score:'))
        print('You obtained a score of ',n)
        break
    except ValueError:
        print('Enter only an integer value')

输出:

Enter your score(without a decimal point):abc
Enter only an integer value
Enter your score(without a decimal point):45.5
Enter only an integer value
Enter your score(without a decimal point):90
You obtained a score of  90

相同程序(无异常处理):

代码:

n=int(input('Enter your score:'))
print('You obtained a score of ',n)

输出:

Enter your score:ninety three
---------------------------------------------------------------------------
ValueError                         Traceback (most recent call last)
<ipython-input-12-aa4fbda9d45f> in <module>

----> 1 n=int(input('Enter your score:'))
      2 print('You obtained a score of ',n)

ValueError: invalid literal for int() with base 10: 'ninety three'

前面代码中有可能导致错误的语句是:int(input('Enter your score:'))int 函数需要一个整数作为参数。如果用户输入一个浮点或字符串值,就会产生一个 ValueError 异常。当我们使用 try-except 构造时, except 子句打印一条消息,要求用户纠正输入,使其更加明确。

使用文件

我们可以使用 Python 中的方法或函数来读写文件。换句话说,我们可以创建一个文件,向其中添加内容或文本,并使用 Python 提供的方法读取其内容。

这里,我们讨论如何读写逗号分隔值(CSV)文件。CSV 文件或逗号分隔文件是文本文件,是 Excel 电子表格的文本版本。

所有这些操作的功能都在 CSV 模块下定义。必须首先使用import csv语句导入这个模块,以使用它的各种方法。

从文件中读取

从 Python 中读取文件包括以下步骤:

  1. 使用with open语句,我们可以打开一个现有的 CSV 文件,并将结果文件对象分配给一个变量或文件句柄(在下面的例子中命名为‘f’)。请注意,我们需要使用绝对路径或相对路径来指定文件的路径。之后,我们需要指定打开文件的模式。对于阅读,模式是“r”。如果我们不指定模式,默认情况下文件是打开读取的。

  2. 接下来是一段代码,使用 csv.reader 函数将文件内容存储在一个 read 对象中,其中我们将文件句柄 f 指定为一个参数。

  3. 但是,这个文件的内容不能通过这个 read 对象直接访问。我们创建一个空列表(在下面的例子中命名为‘contents ’),然后使用 for 循环逐行遍历我们在步骤 2 中创建的 read 对象,并将其追加到这个列表中。然后可以打印该列表,以查看我们创建的 CSV 文件的行。

代码:

#Reading from a file
import csv
with open('animals.csv') as f:
    contents=csv.reader(f)
    lines_of_file=[]
    for line in contents:
        lines_of_file+=line

lines_of_file

写入文件

写入文件包括以下步骤。

  1. 使用打开功能,打开一个现有的 CSV 文件,或者如果文件不存在,打开功能创建一个新文件。如果要覆盖内容或写入新文件,请用引号将文件名(带绝对路径)括起来,并将模式指定为“w”。如果您只想在现有文件中添加一些行,请使用“a”或“append”模式。由于我们不想在这种情况下覆盖,我们使用 append ('a ')模式打开文件。将它存储在一个变量或文件句柄中,并给它一个名字,比如说‘f’。

  2. 使用 csv.writer() 函数,创建一个 writer 对象来添加内容,因为我们不能直接写入 csv 文件。将变量(文件句柄)“f”作为参数传递给此函数。

  3. 对上一步中创建的 writer 对象调用 writerow 方法。传递给该方法的参数是要添加的新行(作为列表)。

  4. 打开系统上的 CSV 文件,查看更改是否已经反映出来。

代码:

#Writing to a file
with open(r'animals.csv',"w") as f:
    writer_object=csv.writer(f,delimiter=",")
    writer_object.writerow(['sheep','lamb'])

open 函数可用于打开文件的模式有:

  • " r ":以只读方式打开文件。

  • “w”:打开一个只写的文件。如果文件已经存在,它将覆盖该文件。

  • “a”:在文件末尾打开文件进行写入。它保留原始文件内容。

  • " w+":打开文件进行读写。

延伸阅读:查看更多关于用 Python 读写文件: https://docs.python.org/3/tutorial/inputoutput.html#reading-and-writing-files

Python 中的模块

模块是扩展名为. py 的 Python 文件。它可以被认为是物理图书馆的一部分。正如图书馆的每个部分(例如,小说、体育、健身)都包含类似性质的书籍一样,模块包含彼此相关的功能。例如, matplotlib 模块包含了所有与绘制图形相关的函数。一个模块也可以包含另一个模块。例如, matplotlib 模块包含一个名为 pyplot 的模块。Python 中有许多内置函数,它们是标准库的一部分,使用它们不需要导入任何模块。

可以使用 import 关键字,后跟模块名称来导入模块:

代码:

import matplotlib

您也可以使用关键字中的导入模块的一部分(子模块或函数)。这里,我们从数学模块导入余弦函数:

代码:

from math import cos

在 Python 中创建和导入定制模块需要以下步骤:

  1. 在“开始”菜单旁边的搜索栏中键入“idle”。Python shell 打开后,选择文件➤新文件创建一个新文件

  2. 创建一些功能相似的函数。作为一个例子,在这里,我们正在创建一个简单的模块,它创建了两个函数——sin _ angle 和 cos_angle。这些函数计算角度的正弦和余弦(以度为单位)。

    CODE:

    import math
    def sin_angle(x):
        y=math.radians(x)
        return math.sin(y)
    def cos_angle(x):
        y=math.radians(x)
        return math.cos(y)
    
    
  3. 保存文件。这个目录是保存文件的地方,也是 Python 运行的目录。您可以使用以下代码获取当前工作目录:

    CODE:

    import os
    os.getcwd()
    
    
  4. 使用 import 语句,导入并使用您刚刚创建的模块。

Python 增强提案(PEP)8–编写代码的标准

Python 增强提案(PEP)是一个技术文档,为 Python 语言中引入的新特性提供文档。PEP 文档有很多种类型,其中最重要的是 PEP 8。PEP 8 文档提供了用 Python 编写代码的风格指南。PEP 8 的主要重点是提供一套增强代码可读性的一致规则——任何阅读你的代码的人都应该能够理解你在做什么。你可以在这里找到完整的 PEP8 文档: https://www.python.org/dev/peps/pep-0008/

PEP8 中有几个针对准则不同方面的指南,其中一些概述如下。

  • 缩进:缩进用来表示代码块的开始。在 Python 中,四个空格用于缩进。缩进时应避免使用制表符。

  • 行长度:一行代码的最大字符长度是 79 个字符。对于注释,限制为 72 个字符。

  • 在 Python 中命名不同类型对象的命名约定也在 PEP 8 中列出。应该使用短名称,并且可以使用下划线来提高可读性。对于命名函数、方法、变量、模块和包,使用小写(全部是小写字母)符号。对于常量,使用大写(全部大写)符号,对于类,使用 CapWords(两个单词都以大写字母开头,不用空格分隔)符号进行命名。

  • 注释:建议使用以#开头并描述整个代码块的块注释。应该避免与代码行在同一行的行内注释,如下所示。如果要使用它们,它们应该用两个空格与代码隔开。

代码:

  • 进口:
sum+=1 #incrementing the sum by 1

当导入一个模块以在代码中使用它时,避免通配符导入(使用*符号),如下所示。

代码:

from module import *

不应在同一行中导入多个包或类。

代码:

import a,b

它们应该在单独的行中导入,如下所示。

代码:

import a
import b

应尽可能使用绝对进口,例如:

代码:

import x.y

或者,我们可以使用这种符号来导入模块:

代码:

  • 编码:用于在 Python 3 中编写代码的编码格式是 UTF-8
from x import y

摘要

  • Python 的语法不同于其他语言,如 Java 和 C,因为 Python 中的语句不以分号结尾,空格(四个空格)用于缩进,而不是花括号。

  • Python 有基本的数据类型,如 intfloatstrbool 等等,还有根据数据类型对变量进行操作的运算符(算术、布尔、赋值和比较)。

  • Python 有 if-elif-else 关键字用于语句的条件执行。它还有用于循环的和用于重复程序特定部分的 while 循环。

  • 函数有助于重用部分代码并避免冗余。每个功能应该只执行一项任务。Python 中的函数是使用 def 关键字定义的。匿名函数或 ?? 函数为在一行中编写函数提供了一种快捷方式,无需将函数绑定到名称上。

  • 模块是相似函数的集合,是一个简单的 Python 文件。除了作为标准库一部分的函数之外,任何作为外部模块一部分的函数都需要使用 import 关键字导入该模块。

  • Python 具有创建、读取和写入文本和 CSV 文件的功能。这些文件可以以各种模式打开,这取决于您是要读取、写入还是追加数据。

  • 异常处理可用于处理程序执行过程中发生的异常。使用 tryexcept 关键字,我们可以处理程序中可能导致异常的部分。

  • PEP 8 为 Python 中一系列与编码相关的方面设定了标准,包括空格、制表符和空行的使用,以及命名和编写注释的约定。

下一章将深入探讨容器、列表、元组、字典和集合等主题。我们还讨论了一种称为面向对象编程的编程范式,以及如何使用类和对象来实现它。

复习练习

问题 1

使用嵌套循环计算数字 1 到 5 的阶乘。

问题 2

使用下列哪一项定义函数?

  1. def 关键字

  2. 功能关键字

  3. void 关键字

  4. 不需要关键字

问题 3

以下代码的输出是什么?

x=True
y=False
z=True
x+y+z

问题 4

编写一个 Python 程序来打印以下序列:

img/498042_1_En_1_Figi_HTML.jpg

问题 5

使用正确的语法定义了这些变量中的哪一个?

  1. 1x=3

  2. x 3=5

  3. x

  4. x_3=5

  5. x$4=5

问题 6

以下代码的输出是什么?(提示:id 函数返回一个对象的内存地址。)

str1="Hello"
str2=str1
id(str1)==id(str2)

问题 7

将字符串“123-456-7890”转换为格式“1234567890”。使用连接拆分字符串功能。

问题 8

编写一个执行以下任务的函数(文件名作为参数传递):

  • 创建一个新的文本文件,其名称作为参数传递给函数

  • 向文件中添加一行文本(“Hello World”)

  • 阅读文件的内容

  • 再次打开文件,在第一行下面添加另一行(“这是下一行”)

  • 重读文件并在屏幕上打印内容

答案

问题 1

解决方案:

#Question 1
for i in range(1,6):
    fact=1
    for j in range(1,i+1):
        fact=fact*j
    print("Factorial of number ",i," is:",fact)

问题 2

选项 Python 中的函数需要 def 关键字。

问题 3

输出:2

解释:布尔值“True”被视为值 1,“False”被视为值 0。对布尔变量应用加法运算符是有效的。

问题 4

解决办法

#question 4
l=range(6)
for i in l[::-1]:
    print("*"*i)
    print("\n")

问题 5

选项 4 是正确的。

让我们一个接一个地检查选项:

  1. 1x=3:不正确,因为变量不能以数字开头

  2. x 3=5:不正确,因为变量名不能包含空格

  3. x:不正确,因为变量需要初始值

  4. x_3=5:正确;定义变量时,下划线是可接受的字符

  5. x\(4=5:不正确;不允许使用像\)这样的特殊字符

问题 6

这两个字符串具有相同的值和内存地址。

输出:

True

问题 7

这个问题可以用一行代码解决——只需拆分字符串,将其转换为一个列表,然后将其连接回一个字符串。

代码:

"".join(list("123-456-7890".split("-")))

问题 8

解决方案:

#Question 8
def filefunction(name):
    #open the file for writing
    with open(name+".txt","w") as f:
        f.write("Hello World")
    #read and print the file contents
    with open(name+".txt","r") as f:
        print(f.read())
    #open the file again the append mode
    with open(name+".txt","a") as f:
        f.write("\nThis is the next line")
    #reread and print the lines in the file
    with open(name+".txt","r") as f:
        print(f.read())
filename=input("Enter the name of the file ")
filefunction(filename)

二、探索容器、类和对象

在这一章中,我们将继续学习 Python 中的其他一些基本概念——各种类型的容器、可用于每个容器的方法、面向对象的编程、类和对象。

容器

在前一章中,我们看到一个变量可以有类似于 intfloatstr 等数据类型,但是只能保存一个值。容器是可以包含多个值的对象。这些值可以具有相同的数据类型,也可以具有不同的数据类型。Python 中的四个内置容器是:

  • 列表

  • 元组

  • 字典

  • 设置

容器也被称为可迭代的;也就是说,您可以访问或遍历给定容器对象中的每个值。

在下面的小节中,我们将更详细地讨论每个容器及其方法。

列表

列表包含一组按顺序存储的值。列表是可变的,也就是说,可以修改、添加或删除列表中的值,使其成为一个灵活的容器。

可以使用索引来访问单个列表项,索引是方括号中的整数,表示该项的位置。列表的索引从 0 开始。

现在让我们来看看如何定义和操作一个列表。

定义列表

可以通过给列表一个名称和一组用方括号括起来的值来定义列表。

代码:

colors=['violet','indigo','red','blue','green','yellow']

向列表添加项目

表 2-1 中说明了不同的方法可用于向列表添加值。在前面的代码中创建的“颜色”列表用于下表给出的示例中。

表 2-1

向列表中添加项目

|

方法

|

描述

|

例子

|
| --- | --- | --- |
| 附加 | 在列表末尾添加一项。该方法只接受一个值作为参数。 | 代码:colors.append('white')``#the value 'white' is added after the last item in the 'colors' list |
| 插入 | 在给定位置或索引处添加一项。这个方法有两个参数——索引和要添加的值。 | 代码:colors.insert(3,'pink')``#the value 'pink' is added at the fourth position in the 'colors' list |
| 扩展 | 在给定列表的末尾添加多个元素(作为一个列表)。该方法将另一个列表作为参数。 | 代码:colors.extend(['purple','magenta'])``#values 'purple' and 'magenta' added at the end of the 'colors' list |

从列表中删除元素

正如有多种方法可以向列表中添加项目一样,也有多种方法可以从列表中删除值,如表 2-2 中所述。请注意,每种方法一次只能删除一个项目。

表 2-2

从列表中删除项目

|

方法

|

描述

|

例子

|
| --- | --- | --- |
| 的 | 关键字删除给定位置的项目。 | 代码:del colors[1]``#removes the second item of the 'colors' list |
| 移除 | 当已知要移除的项的名称,但不知道其位置时,使用此方法。 | 代码:colors.remove('white')``#removes the item 'white' from the 'colors' list |
| 流行 | 此方法移除并返回列表中的最后一项。 | 代码:colors.pop()``#removes the last item and displays the item removed |

查找列表中对象的索引(位置)

index 方法用于找出列表中特定项或值的位置(或索引),如以下语句所示。

代码:

colors.index('violet')

输出:

0

计算列表长度

len 函数返回列表中的项目数,如下所示。列表的名称作为参数传递给这个函数。注意 len 是一个函数,不是一个方法。方法只能用于对象。

代码:

len(colors)

输出:

7

排序列表

sort 方法对列表中的值进行升序或降序排序。默认情况下,此方法按升序对项目进行排序。如果列表包含字符串,则按字母顺序进行排序(使用每个字符串的第一个字母),如下所示。

代码:

colors.sort()
colors

输出:

['blue', 'green', 'purple', 'red', 'violet', 'white', 'yellow']

请注意,列表必须是同类的(列表中的所有值都应该是相同的数据类型),排序方法才能起作用。如果列表包含不同数据类型的项目,应用 sort 方法会导致错误。

如果您想按相反的字母顺序对元素进行排序,您需要添加 reverse 参数,并将其设置为“True”,如下所示。

代码:

colors.sort(reverse=True)
colors

输出:

['yellow', 'white', 'violet', 'red', 'purple', 'green', 'blue']

请注意,如果您只想颠倒列表中项目的顺序,而不想对项目进行排序,您可以使用 reverse 方法,如下所示。

代码:

colors=['violet','indigo','red','blue','green','yellow']
colors.reverse()
colors

输出:

['yellow', 'green', 'blue', 'red', 'indigo', 'violet']

延伸阅读:

查看更多可用于列表的方法:

https://docs.python.org/3/tutorial/datastructures.html#more-on-lists

切片列表

当我们从列表中创建切片时,我们通过从列表中选择特定的值,基于它们的位置或者通过使用一个条件,来创建原始列表的子集。列表切片的工作方式类似于字符串切片,我们在上一章已经研究过了。

为了使用索引创建切片,我们使用冒号操作符(:)并为需要选择的索引指定起始值和终止值。

如果我们在冒号前后都没有提供开始或停止值,则假定开始值是第一个元素的索引(0),而停止值是最后一个元素的索引,如下面的语句所示。

代码:

`colors[:]

输出:

['Violet', 'Indigo', 'Blue', 'Green', 'Yellow', 'Orange', 'Red']

如果我们使用步进索引,我们也可以使用冒号操作符两次。在下面的语句中,通过指定步长索引为 2 来提取列表的替代元素。

代码:

colors[::2]

输出:

['Violet', 'Blue', 'Yellow', 'Red']

就像字符串一样,列表遵循正索引和负索引。负索引(从–1 开始,是列表中最后一个元素的索引)从右到左工作,而正索引(从 0 开始,是列表中第一个元素的索引)从左到右工作。

下面显示了一个使用负索引的切片示例,其中我们从列表中的最后一个值开始提取替代元素。

代码:

colors[-1:-8:-2]

输出:

['Red', 'Yellow', 'Blue', 'Violet']

从现有列表创建新列表

从现有列表创建新列表有三种方法——列表综合、映射函数和过滤器函数——下面将对此进行解释。

  1. 列出理解

List comprehensions 提供了一种从现有列表创建新列表的简单而直观的方法。

让我们通过一个例子来理解这一点,在这个例子中,我们从前面创建的列表(“colors”)中创建一个新的列表(“colors1”)。此列表仅包含原始列表中包含字母“e”的项目。

代码:

colors1=[color for color in colors if 'e' in color]
colors1

输出:

['violet', 'red', 'blue', 'green', 'yellow']

列表理解的结构如图 2-1 所示。新列表中项目的输出表达式(' color ')首先出现。接下来是一个 for 循环来遍历原始列表(注意,其他循环,如 while 循环,在列表理解中不用于迭代)。最后,您可以使用 if/else 结构添加一个可选条件。

img/498042_1_En_2_Fig1_HTML.png

图 2-1

列表理解

如果我们试图使用循环和条件,在没有列表理解的情况下创建相同的列表,代码将会更加扩展,如下所示。

代码:

colors1=[]
for color in colors:
    if 'e' in color:
        colors1.append(color)

在使用列表理解时要记住的关键点是代码的可读性不应该受到损害。如果在创建新列表的过程中涉及到太多的条件表达式和循环,最好避免列表理解。

延伸阅读:查看更多关于列表理解: https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions

  1. 地图功能

    map 函数用于通过在现有列表上应用用户定义的或内置的函数来创建新列表。 map 函数返回一个地图对象,我们需要应用 list 函数将其转换成一个列表。

    The map function accepts two arguments:

    • 要应用的函数

    • 要应用该函数的列表

在下面的示例中,我们从“colors”列表创建一个新列表(“colors1”),将其元素转换为大写。使用了一个匿名(lambda)函数,后跟原始列表的名称。

代码:

colors=['violet','indigo','red','blue','green','yellow']
colors1=map(lambda x:x.upper(),colors)
colors1

输出:

<map at 0x2dc87148630>

该函数返回一个 map 对象,需要使用 list 函数将其转换为 list。

代码:

list(colors1)

输出:

  1. 过滤功能
['VIOLET', 'INDIGO', 'RED', 'BLUE', 'GREEN', 'YELLOW']

过滤器函数的语法类似于地图函数的语法。映射函数在应用后返回原始列表中的所有对象,而过滤器函数仅返回那些满足调用过滤器函数时指定的条件的项目。与 map 函数类似,我们传递两个参数(一个 lambda 函数或一个用户定义的函数,后跟列表的名称)。

在下面的例子中,我们从原始列表中创建一个列表,只保留那些少于五个字符的条目。

代码:

colors2=filter(lambda x:len(x)<5,colors)
list(colors2)

输出:

['red', 'blue']

现在让我们讨论如何同时遍历两个或更多的列表。

使用 zip 函数遍历多个列表

zip 函数提供了一种组合列表并在这些列表上联合执行操作的方法,如下所示。需要组合的列表作为参数传递给 list 函数。

代码:

#zip function and lists
colors=['Violet','Indigo','Blue','Green','Yellow','Orange','Red']
color_ids=[1,2,3,4,5,6,7]
for i,j in zip(colors, color_ids):
    print(i,"has a serial number",j)

输出:

Violet has a serial number 1
Indigo has a serial number 2
Blue has a serial number 3
Green has a serial number 4
Yellow has a serial number 5
Orange has a serial number 6
Red has a serial number 7

zip 函数返回存储在“zip”类型对象中的元组列表。该对象的类型需要更改为列表类型才能查看元组。

代码:

list(zip(colors,color_ids))

输出:

[('Violet', 1),
 ('Indigo', 2),
 ('Blue', 3),
 ('Green', 4),
 ('Yellow', 5),
 ('Orange', 6),
 ('Red', 7)]

下一个函数, enumerate ,帮助我们访问列表中条目的索引。

访问列表中项目的索引

当你想访问一个给定列表中的对象及其索引时,枚举函数很有用。该函数返回一系列元组,每个元组包含该项及其索引。该函数的用法示例如下所示。

代码:

colors=['Violet','Indigo','Blue','Green','Yellow','Orange','Red']
for index,item in enumerate(colors):
    print(item,"occurs at index",index)

输出:

Violet occurs at index 0
Indigo occurs at index 1
Blue occurs at index 2
Green occurs at index 3
Yellow occurs at index 4
Orange occurs at index 5
Red occurs at index 6

列表的连接

列表的连接,即我们组合两个或多个列表,可以使用'+'操作符来完成。

代码:

x=[1,2,3]
y=[3,4,5]
x+y

输出:

[1, 2, 3, 3, 4, 5]

我们可以连接任意数量的列表。请注意,串联不会修改任何被连接的列表。串联操作的结果存储在一个新的对象中。

extend 方法也可以用于列表的连接。与'+'操作符不同,扩展方法改变了原始列表。

代码:

x.extend(y)
x

输出:

[1, 2, 3, 3, 4, 5]

其他算术运算符,如-、*或/,不能用于组合列表。

为了找出两个包含数字的列表中元素的区别,我们使用 list comprehension 和 zip 函数,如下所示。

代码:

x=[1,2,3]
y=[3,4,5]
d=[i-j for i,j in zip(x,y)]
d

输出:

[-2, -2, -2]

元组

tuple 是 Python 中的另一个容器,和 list 一样,它按顺序存储项目。像列表中的项目一样,元组中的值可以通过它们的索引来访问。然而,元组的一些属性使其不同于列表,如下所述。

  1. 不变性:tuple 是不可变的,这意味着您不能添加、删除或更改 tuple 中的元素。另一方面,列表允许所有这些操作。

  2. 语法:定义元组的语法使用圆括号(或圆括号)将单个值括起来(与列表使用的方括号相比)。

  3. 速度:就访问和检索单个元素的速度而言,一个元组比一个列表表现得更好。

现在让我们学习如何定义一个元组以及可以用于元组的各种方法。

定义一个元组

元组可以定义为带括号或不带括号,如下面的代码所示。

代码:

a=(1,2,3)
#can also be defined without parenthesis
b=1,2,3
#A tuple can contain just a simple element
c=1,
#Note that we need to add the comma even though there is no element following it because we are telling the interpreter that it is a tuple.

就像列表一样,元组可以包含任何内置数据类型的对象,比如浮点、字符串、布尔值等等。

用于元组的方法

虽然元组不能更改,但有一些操作可以用元组来执行。这些操作解释如下。

元组中对象的频率

count 方法用于计算元组中给定值的出现次数:

代码:

x=(True,False,True,False,True)
x.count(True)

输出:

3

元组项目的位置/索引

index 方法可以用来查找一个元组中一个条目的位置。使用前面语句中创建的元组,让我们找到值“False”的出现。

代码:

x.index(False)

输出:

1

index 方法只返回该项第一次出现的位置。

元组解包

元组解包是提取元组中的单个值并将这些项中的每一项存储在单独的变量中的过程。

代码:

a,b,c,d,e=x

如果我们不知道元组中项的数量,我们可以使用“*_”符号将出现在第一个元素之后的元素解包到一个列表中,如下所示。

代码:

a,*_=x
print(a,_)

输出:

True [False, True, False, True]

一个元组的长度

可以使用 len 函数计算元组的长度:

代码:

len(x)

输出:

5

元组切片

可以对元组执行值的更小子集的切片或创建(在这方面类似于列表和字符串)。

下面是一个例子。

代码:

x[::-1]

输出:

(True, False, True, False, True)

元组的应用

下面是一些可以使用元组的场景。

  1. 用元组创建字典

我们将在下一节详细讨论字典,它是一个包含一组条目的容器(有一个映射到值的键)。元组可用于在创建字典时定义条目。

字典项是一个元组,可以使用 dict 方法将字典定义为一个元组列表,如下所示。

代码:

x=dict([('color','pink'),('flower','rose')])
x

输出:

  1. 多重分配
{'color': 'pink', 'flower': 'rose'}

如前所述,元组解包是将元组分成其组件的过程。这个原则可以用于在一行中初始化多个变量,如下所示。

代码:

#tuple unpacking
(a,b,c,d)=range(4)
print(a,b,c,d)

输出:

0 1 2 3

延伸阅读:在此查看更多关于元组: https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences

字典

字典是包含一组条目的容器,每个条目将一个“键”映射到一个“值”。每个单独的项也称为键/值对。关于字典,还有其他一些需要注意的地方:

  • 与列表和元组中的值不同,字典中的项不是按顺序存储的。

  • 像列表一样,字典是可变的(即,可以对字典对象进行修改)。

  • 花括号用于将字典中的条目括起来。

让我们了解如何定义一个字典以及可以用于字典对象的不同方法。

定义字典

字典被定义为一对花括号内的一组逗号分隔的键/值对,如下面的代码所示。

代码:

numbers={'English':'One','Spanish':'Uno','German':'Ein'}
numbers

输出:

{'English': 'One', 'Spanish': 'Uno', 'German': 'Ein'}

“英语”、“西班牙语”和“德语”构成了键,而“One”、“Uno”和“Ein”是字典中的值。

还可以使用 dict 函数来定义字典,正如我们在前面讨论元组时所解释的。这个函数的参数是一个元组列表,每个元组代表一个键/值对,如下所示。

numbers=dict([('English','One'),('Spanish','Uno'),('German','Ein')])

向字典添加条目(键/值对)

使用该键作为索引,可以向字典中添加一个新项,如下所示。

代码:

numbers['French']='un'
numbers
#A new key/value pair with the key as 'French' and value as 'un' has been added.

输出:

{'English': 'One', 'Spanish': 'Uno', 'German': 'Ein', 'French': 'un'}

访问字典中的关键字

keys 方法访问字典中的键:

代码:

numbers.keys()

输出:

dict_keys(['English', 'Spanish', 'German', 'French'])

访问字典中的值

values 方法访问字典中的值:

代码:

numbers.values()

输出:

dict_values(['One', 'Uno', 'Ein', 'un'])

访问字典中的所有键/值对

items 方法用于访问字典中的键/值对列表。

代码:

numbers.items()

输出:

dict_items([('English', 'One'), ('Spanish', 'Uno'), ('German', 'Ein'), ('French', 'un')])

访问单个值

可以使用给定键作为索引来检索对应于该键的值,如下所示。

代码:

numbers['German']

输出:

'Ein'

get 方法也可以用于检索值。该密钥作为参数传递给此方法,如下所示。

代码:

numbers.get('German')

输出与前面语句中获得的输出相同。

设置键的默认值

前面讨论的 get 方法也可以用来添加一个键/值对,并为一个键设置默认值。如果已经定义了键/值对,则默认值将被忽略。还有另一个方法, setdefault ,也可以用于这个目的。

注意, get 方法并不改变字典对象,而 setdefault 方法确保这些改变反映在对象中。

setdefault 方法的用法示例如下所示。

代码:

numbers.setdefault('Mandarin','yi')
numbers

输出:

{'English': 'One',
 'Spanish': 'Uno',
 'German': 'Ein',
 'French': 'un',
 'Mandarin': 'yi'}

正如我们所看到的,添加了一个新的键/值对。

下面显示了一个 get 方法的例子。

代码:

numbers.get('Hindi','Ek')
numbers

输出:

{'English': 'One',
 'Spanish': 'Uno',
 'German': 'Ein',
 'French': 'un',
 'Mandarin': 'yi'}

由 g et 方法设置的值不会添加到字典中。

清理字典

clear 方法从字典中删除所有的键/值对,换句话说,它清除字典的内容,而不从内存中删除变量。

#removing all the key/value pairs
numbers.clear()

输出:

  {}

延伸阅读:查看更多关于词典的信息:

https://docs.python.org/3/tutorial/datastructures.html#dictionaries

设置

集合是一个容器,其中包含未排序或未索引的元素。集合的主要特征是它是由个唯一的元素组成的集合。请注意,如果我们在创建集合时添加重复的元素,Python 不会抛出错误。然而,当我们对集合执行操作时,所有重复的元素都被忽略,只考虑不同的元素。

设定定义

就像字典一样,集合是用花括号声明的,并且有无序的元素。然而,虽然可以使用关键字作为索引来访问字典中的值,但是不能通过索引来访问集合中的值。

以下是集合定义的示例:

代码:

a={1,1,2}
a

输出:

{1, 2}

正如我们从输出中看到的,重复值 1(存在于集合定义中)被忽略。

设定操作

表 2-3 中解释了可用于器械包的方法和功能。

延伸阅读:查看更多关于集: https://docs.python.org/3/tutorial/datastructures.html#sets

表 2-3

集合操作

|

操作

|

方法/功能

|

例子

|
| --- | --- | --- |
| 求集合的长度 | 函数计算集合中元素的数量,只考虑不同的值。 | len(a) |
| 集合迭代 | for 循环可以遍历一个集合并打印它的元素。 | for x in a:``print(x) |
| 添加项目或值 | 使用添加方法可以将单个物品添加到器械包中。对于添加多个值,使用更新方法。 | a.add(3)``#or``a.update([4,5]) |
| 移除项目 | 可以使用移除丢弃方法移除物品。 | a.remove(4)``# Or``a.discard(4)``#Note: When we try to delete an element that is not in the set, the discard method does not give an error, whereas the remove method gives a KeyError. |

既然我们已经涵盖了 Python 语言的所有要素——我们在前一章中学习的概念以及我们在本章中了解的关于各种容器及其方法的内容,我们需要决定使用哪种风格或编程范式。在各种编程范例中,包括过程式编程、函数式编程和面向对象编程,我们将在下一节讨论面向对象编程。

面向对象编程

面向对象编程(通常也称为“OOPS”)是作为传统编程方法的过程编程的替代方案出现的。

程序设计包括使用一系列步骤顺序执行程序。过程化编程的一个主要缺点是全局变量的存在容易受到意外操纵。OOPS 相对于过程化编程提供了几个优势,包括重用代码的能力、消除全局变量、防止未经授权的数据访问,以及提供管理代码复杂性的能力。

Python 遵循面向对象的范式。类和对象构成了面向对象编程的构造块。类提供了蓝图或结构,而对象实现了这个结构。使用 class 关键字定义类。

例如,假设您有一个名为“Baby”的类,其属性包括婴儿的名字、性别和体重。这个类的方法(或类中定义的函数)可以是婴儿执行的动作,如笑、哭和吃。实例/对象是类的实现,有自己的一组属性和方法。在这个例子中,每个婴儿都有其独特的特征(数据)和行为(功能)

一个类可以有一组属性或变量,可以是类变量,也可以是实例变量。类的所有实例共享类变量,而实例变量对于每个实例是唯一的。

让我们看看如何在 Python 中定义类,使用下面的例子:

代码:

#example of a class
class Rectangle:
    sides=4
    def __init__(self,l,b):
        self.length=l
        self.breadth=b
    def area(self):
        print("Area:",self.length*self.breadth)
my_rectangle=Rectangle(4,5)
my_rectangle.area()

输出:

Area: 20

class 关键字后跟一个类名和一个冒号。接下来,我们定义一个名为“sides”的类变量,并将其初始化为 4。该变量对该类的所有对象都是通用的。

在这之后,有一个设置或初始化变量的构造函数。注意构造函数的特殊语法——空格跟在关键字 def 后面,然后是两个下划线符号, init 关键字,再后面是两个下划线符号。

在一个类中定义的任何方法的第一个参数是 self 关键字,它引用该类的一个实例。然后是初始化参数“l”和“b”,它们指的是矩形的长度和宽度。这些值是在我们创建对象时作为参数提供的。实例变量“self.length”和“self . width”使用前面提到的参数进行初始化。接下来是另一个计算矩形面积的方法。记住,每当我们定义一个类的任何方法时,我们都需要添加 self 作为参数。

一旦定义了类,我们就可以定义这个类的一个实例,也称为对象。我们创建一个对象就像我们创建一个变量,给它一个名字,并初始化它。“my_rectangle”是创建的对象/实例的名称,后跟一个“=”符号。然后我们提到类的名字和在构造函数中使用的参数来初始化对象。我们正在创建一个长为 4,宽为 5 的矩形。然后我们调用 area 方法来计算面积,它计算并打印面积。

延伸阅读:查看更多关于 Python 中的类: https://docs.python.org/3/tutorial/classes.html

面向对象编程原则

面向对象编程的主要原则是封装、多态、数据抽象和继承。让我们来看看这些概念。

封装:封装是指将数据(类中定义的变量)与可以修改它的功能(方法)绑定在一起。封装还包括数据隐藏,因为类内定义的数据不会被类外定义的任何函数操纵。一旦我们创建了该类的一个对象,它的变量只能由与该对象相关联的方法(或函数)来访问和修改。

让我们考虑下面的例子:

代码:

class Circle():
    def __init__(self,r):
        self.radius=r
    def area(self):
        return 3.14*self.r*self.r
c=Circle(5)
c.radius #correct way of accessing instance variable

这里,Circle 类有一个实例变量 radius 和一个方法 area。变量 radius 只能使用该类的对象访问,而不能通过任何其他方式访问,如下面的语句所示。

代码:

c.radius #correct way of accessing instance variable
radius #incorrect, leads to an error

多态性

多态(一个接口,多种形式)提供了使用相同接口(方法或函数)的能力,而不管数据类型如何。

让我们使用 len 函数来理解多态性的原理。

代码:

#using the len function with a string
len("Hello")

输出:

5

代码:

#using the len function with a list
len([1,2,3,4])

输出:

4

代码:

#using the len function with a tuple
len((1,2,3))

输出:

3

代码:

#using the len function with a dictionary
len({'a':1,'b':2})

输出:

2

计算其参数长度的 len 函数可以接受任何类型的参数。我们将字符串、列表、元组和字典作为参数传递给该函数,该函数返回这些对象的长度。没有必要为每种数据类型编写单独的函数。

继承:继承指的是从父类创建另一个类的能力,称为子类。子类继承了父类的一些属性和功能,但也可能有自己的功能和变量。

下面演示了 Python 中继承的一个例子。

#inheritance
class Mother():
    def __init__(self,fname,sname):
        self.firstname=fname
        self.surname=sname
    def nameprint(self):
        print("Name:",self.firstname+" "+self.surname)
class Child(Mother):
    pass

父类称为“Mother”,其属性“firstname”和“surname”使用 init 构造函数方法初始化。名为“child”的子类继承自“Mother”类。当我们定义子类时,父类的名称作为参数传递。关键字 pass 指示 Python 不需要为子类做任何事情(这个类只是从父类继承一切,而不添加任何东西)。

然而,即使子类没有实现任何其他方法或添加任何额外的属性,关键字 pass 对于防止抛出任何错误是必不可少的。

延伸阅读:了解更多传承: https://docs.python.org/3/tutorial/classes.html#inheritance

数据抽象

数据抽象是只显示功能而隐藏实现细节的过程。例如,Whatsapp 的新用户只需要学习它的基本功能,如发送消息、附加照片和拨打电话,而不必学习编写该应用程序代码的开发人员如何实现这些功能。

在下面的例子中,我们声明了一个“Circle”类的对象并使用 area 方法计算面积,当我们调用 area 方法时,我们不需要知道面积是如何计算的。

class Circle():
    def __init__(self,r):
        self.r=r
    def area(self):
        return 3.14*self.r*self.r
circle1=Circle(3)
circle1.area()

输出:

28.259999999999998

摘要

  • 容器是属于基本数据类型(如 int、float、str)的对象集合。Python 中有四个内置容器——列表、元组、字典和集合。

  • 每个容器都有不同的属性,可以应用于容器的各种函数也各不相同,这取决于元素是否可以排序和更改(可变性)。列表是可变的和有序的,元组是不可变的和有序的,字典和集合是可变的和无序的。

  • Python 遵循面向对象编程的原则,如继承(从另一个类派生一个类)、数据抽象(仅呈现相关细节)、封装(将数据与功能绑定)和多态(使用多种数据类型的接口的能力)。

  • 一个类包含一个构造函数(使用特殊的语法定义)、实例变量和操作这些变量的方法。所有方法都必须包含关键字 self 作为引用该类对象的参数。

在下一章,我们将学习 Python 如何应用于正则表达式和解决数学问题,以及用于这些应用的库。

复习练习

问题 1

如何将列表转换成元组,反之亦然?

问题 2

就像列表理解一样,字典理解是从现有条目创建字典的捷径。使用字典理解创建以下字典(从两个列表中,一个包含键(a–f),另一个包含值(1–6)):

{'a': 0, 'b': 1, 'c': 2, 'd': 3, 'e': 4, 'f': 5}

问题 3

下面哪个代码语句不是会导致错误?

  1. 'abc'[0]='d'

  2. list('abc')[0]='d'

  3. tuple('abc')[0]='d'

  4. dict([('a',1),('b',2)])[0]=3

问题 4

写一个程序,计算“乌云背后都有一线光明”这句话的元音数。

问题 5

以下代码的输出是什么?

x=1,2
y=1,
z=(1,2,3)
type(x)==type(y)==type(z)

问题 6

以下代码的输出是什么?

numbers={
    'English':{'1':'One','2':'Two'},
    'Spanish':{'1':'Uno','2':'Dos'},
    'German':{'1':'Ein','2':'Zwei'}
}
numbers['Spanish']['2']

问题 7

考虑下面的字典:

eatables={'chocolate':2,'ice cream':3}

使用将另一个条目(关键字为“biscuit ”,值为 4)添加到这个字典中

  • 如果语句

  • setdefault 方法

问题 8

创建一个包含从 1 到 20 的奇数的列表,并使用适当的 list 方法执行以下操作:

  • 在末尾添加元素 21

  • 在第四个位置插入数字 23

  • 向此列表中添加另一个包含 1 到 20 的偶数的列表

  • 找出数字 15 的索引

  • 移除并返回最后一个元素

  • 删除第 10 个元素

  • 筛选此列表以创建所有数字小于或等于 13 的新列表

  • 使用 map 函数创建一个新列表,其中包含列表中数字的方块

  • 使用列表理解从现有列表创建新列表。如果是奇数,这个列表应该包含原始数字。否则它应该包含该数字的一半。

答案

问题 1

使用 list 方法将元组转换为列表:

list((1,2,3))

使用 tuple 方法将列表转换为元组:

tuple([1,2,3])

问题 2

代码:

#list containing keys
l=list('abcdef')
#list containing values
m=list(range(6))
#dictionary comprehension
x={i:j for i,j in zip(l,m)}
x

问题 3

正确选项:b 和 d

在选项 a 和 c 中,代码语句试图分别改变字符串和元组中的项目,它们是不可变的对象,因此这些操作是不允许的。在选项 b(列表)和 d(词典)中,允许项目分配。

问题 4

解决方案:

message="Every cloud has a silver lining"
m=message.lower()
count={}
vowels=['a','e','i','o','u']
for character in m:
    if character.casefold() in vowels:
        count.setdefault(character,0)
        count[character]=count[character]+1
print(count)

问题 5

输出:

True

这三种方法都是公认的定义元组的方式。

问题 6

输出:

'Dos'

这个问题使用了嵌套字典(字典中的字典)的概念。

问题 7

解决方案:

eatables={'chocolate':2,'ice cream':3}
#If statement
if 'biscuit' not in eatables:
    eatables['biscuit']=4
#setdefault method(alternative method)
eatables.setdefault('biscuit',4)

问题 8

解决方案:

odd_numbers=list(range(1,20,2))
#Add the element 21 at the end
odd_numbers.append(21)
#insert the number 23 at the 4th position
odd_numbers.insert(3,23)
#To this list, add another list containing even numbers from 1 to 20
even_numbers=list(range(2,20,2))
odd_numbers.extend(even_numbers)
#find the index of the number 15
odd_numbers.index(15)
#remove and return the last element
odd_numbers.pop()
#delete the 10the element
del odd_numbers[9]
#filter this list with all numbers less than or equal to 13
nos_less_13=filter(lambda x:x<=13,odd_numbers)
list(nos_less_13)
#use the map function to create a list containing squares
squared_list=map(lambda x:x**2,odd_numbers)
#use list comprehension for the new list
new_list=[x/2 if x%2==0 else x for x in odd_numbers]
new_list

三、Python 中的正则表达式和数学

在这一章中,我们讨论 Python 中的两个模块: re ,它包含可应用于正则表达式的函数,以及 SymPy ,用于解决代数、微积分、概率和集合论中的数学问题。我们将在本章中学习的概念,如搜索和替换字符串、概率和绘制图表,将在后续章节中派上用场,我们将在后续章节中介绍数据分析和统计。

正则表达式

正则表达式是包含字符(如字母和数字)和元字符(如*和$符号)的模式。每当我们想要搜索、替换或提取具有可识别模式的数据时,都可以使用正则表达式,例如日期、邮政编码、HTML 标记、电话号码等等。通过确保用户输入的格式正确,它们还可以用于验证密码和电子邮件地址等字段。

使用正则表达式解决问题的步骤

Python 中的 re 模块提供了对正则表达式的支持,可以使用以下语句导入该模块:

import re

如果您还没有安装 re 模块,请进入 Anaconda 提示符并输入以下命令:

pip install re

一旦模块被导入,您需要遵循以下步骤。

  1. 定义并编译正则表达式:re 模块导入后,我们定义正则表达式并编译。搜索模式以前缀“r”开头,后跟字符串(搜索模式)。前缀“r”代表原始字符串,它告诉编译器特殊字符将按字面意思处理,而不是转义序列。请注意,前缀“r”是可选的。compile 函数将搜索模式编译成字节码,如下所示,搜索字符串(和)作为参数传递给 compile 函数。

    CODE:

    search_pattern=re.compile(r'and')
    
    
  2. 在字符串中定位搜索模式(正则表达式):

    在第二步中,我们尝试使用 search 方法在要搜索的字符串中定位这个模式。这个方法是对我们在上一步中定义的变量(search_pattern)调用的。

    CODE:

    search_pattern.search('Today and tomorrow')
    
    

    Output:

    <re.Match object; span=(6, 9), match="and">
    
    

因为在字符串(“Today and tomorrow”)中找到了搜索模式(“and”),所以返回一个 match 对象。

快捷方式(结合步骤 2 和 3)

前面的两个步骤可以合并成一个步骤,如下面的语句所示:

代码:

re.search('and','Today and tomorrow')

使用前面定义的一行代码,我们将定义、编译和定位搜索模式这三个步骤合并为一个步骤。

延伸阅读:参考本文档,了解如何在 Python 中使用正则表达式:

https://docs.python.org/3/howto/regex.html#regex-howto

正则表达式的 Python 函数

我们使用正则表达式来匹配、拆分和替换文本,并且这些任务中的每一个都有单独的函数。表 3-1 提供了所有这些功能的列表,以及它们的用法示例。

表 3-1

在 Python 中使用正则表达式的函数

|

Python 函数

|

例子

|
| --- | --- |
| re.findall( ) :搜索正则表达式的所有可能匹配项,并返回在字符串中找到的所有匹配项的列表。 | 代码:re.findall('3','98371234')输出:['3', '3'] |
| re.search( ) :搜索单个匹配项,并返回与字符串中找到的第一个匹配项相对应的 match 对象。 | 代码:re.search('3','98371234')输出:<re.Match object; span=(2, 3), match="3"> |
| re.match( ) :该功能类似于重新搜索功能。这个函数的限制是,如果模式出现在字符串的开头的,它只返回一个匹配对象。** | 代码:re.match('3','98371234')因为搜索模式(3)不在字符串的开头,所以 match 函数不返回对象,我们看不到任何输出。 |
| re.split( ): 在被搜索字符串中找到搜索模式的位置拆分字符串。 | 代码:re.split('3','98371234')输出:['98', '712', '4']只要找到搜索模式“3”,该字符串就会被拆分成更小的字符串。 |
| re():用另一个字符串或模式替换搜索模式。 | 代码:re.sub('3','three','98371234')输出:'98three712three4'字符串中的字符“3”被替换为字符串“三”。 |

延伸阅读:

了解上表中讨论的函数的更多信息:

元字符

元字符是正则表达式中使用的具有特殊含义的字符。下面将解释这些元字符,并举例说明它们的用法。

  1. 点(。)元字符

    这个元字符匹配一个字符,这个字符可以是一个数字、字母,甚至是它本身。

    在下面的例子中,我们尝试匹配三个字母的单词(来自下面代码中逗号后面给出的列表),从两个字母“ba”开始。

    CODE:

    re.findall("ba.","bar bat bad ba. ban")
    
    

    Output:

    ['bar', 'bat', 'bad', 'ba.', 'ban']
    
    

    请注意,输出中显示的结果之一是“ba”是一个实例,其中。(点)元字符已匹配自身。

  2. 方括号([])作为元字符

    为了匹配一组字符中的任何一个字符,我们使用方括号([ ])。在这些方括号中,我们定义了一组字符,其中一个字符必须与文本中的字符相匹配。

    让我们用一个例子来理解这一点。在下面的示例中,我们尝试匹配包含字符串“ash”的所有字符串,并以下列任何字符开始-“c”、“r”、“b”、“m”、“d”、“h”或“w”。

    CODE:

    regex=re.compile(r'[crbmdhw]ash')
    regex.findall('cash rash bash mash dash hash wash crash ash')
    
    

    Output:

    ['cash', 'rash', 'bash', 'mash', 'dash', 'hash', 'wash', 'rash']
    
    

    请注意,字符串“ash”和“crash”不匹配,因为它们不符合标准(字符串需要以方括号中定义的字符之一开头)。

  3. 问号(?)元字符

    当您需要匹配一个字符的最多一次出现时,使用这个元字符。这意味着我们要寻找的字符可能不在搜索字符串中,或者只出现一次。考虑下面的例子,我们试图匹配以字符“Austr”开始,以字符“ia”结束,并且以下每个字符零个或一个出现的字符串——“a”、“l”、“a”、“s”。

    CODE:

    regex=re.compile(r'Austr[a]?[l]?[a]?[s]?ia')
    regex.findall('Austria Australia Australasia Asia')
    
    

    Output:

    ['Austria', 'Australia', 'Australasia']
    
    

    请注意,字符串“Asia”不符合此标准。

  4. 星号(*)元字符

    这个元字符可以匹配零个或多个给定的搜索模式。换句话说,搜索模式可能根本不会出现在字符串中,也可能出现任意次。

    让我们通过一个例子来理解这一点,在这个例子中,我们试图匹配所有以字符串“abc”开头,后面跟有零个或多个数字“1”的字符串。

    CODE:

    re.findall("abc[1]*","abc1 abc111 abc1 abc abc111111111111 abc01")
    
    

    Output:

    ['abc1', 'abc111', 'abc1', 'abc', 'abc111111111111', 'abc']
    
    

    请注意,在这一步中,我们将正则表达式的编译和搜索合并在一个单独的步骤中。

  5. 反斜杠()元字符

    The backslash symbol is used to indicate a character class, which is a predefined set of characters. In Table 3-2, the commonly used character classes are explained.

    表 3-2

    字符类别

    |

    字符类

    |

    涵盖的字符

    |
    | --- | --- |
    | \d | 匹配一个数字(0–9) |
    | \D | 匹配任何不是数字的字符 |
    | \w | 匹配字母数字字符,可以是小写字母(A–Z)、大写字母(A–Z)或数字(0–9) |
    | \W | 匹配任何不是字母数字的字符 |
    | \s | 匹配任何空白字符 |
    | \S | 匹配任何非空白字符 |

    反斜杠符号的另一种用法:转义元字符

    正如我们所见,在正则表达式中,元字符如。和*有特殊含义。如果我们想在字面上使用这些字符,我们需要通过在这些字符前面加上一个(反斜杠)符号来“转义”它们。例如,要搜索文本 W.H.O,我们需要对。(点)字符,以防止它被用作常规元字符。

    CODE:

    regex=re.compile(r'W\.H\.O')
    regex.search('W.H.O norms')
    
    

    Output:

    <re.Match object; span=(0, 5), match='W.H.O'>
    
    
  6. 加号(+)元字符

    此元字符匹配一个或多个搜索模式。在下面的示例中,我们尝试匹配所有以至少一个字母开头的字符串。

    CODE:

    re.findall("[a-z]+123","a123 b123 123 ab123 xyz123")
    
    

    Output:

    ['a123', 'b123', 'ab123', 'xyz123']
    
    
  7. 花括号{}作为元字符

    使用花括号并在花括号中指定一个数字,我们可以指定一个范围或一个数字来表示搜索模式的重复次数。

    在下面的例子中,我们找出了所有格式为“xxx-xxx-xxxx”的电话号码(三个数字,接着是另一组三个数字,最后是一组四个数字,每组数字用“-”号分隔)。

    CODE:

    regex=re.compile(r'[\d]{3}-[\d]{3}-[\d]{4}')
    regex.findall('987-999-8888 99122222 911-911-9111')
    
    

    Output:

    ['987-999-8888', '911-911-9111']
    
    

    只有搜索字符串(987-999-8888, 911-911-9111)中的第一个和第三个数字与模式匹配。\d 元字符代表一个数字。

    如果我们不知道重复次数的确切数字,但知道最大和最小重复次数,我们可以在花括号内提到上限和下限。在下面的示例中,我们搜索包含最少六个字符、最多十个字符的所有字符串。

    CODE:

    regex=re.compile(r'[\w]{6,10}')
    regex.findall('abcd abcd1234,abc$$$$$,abcd12 abcdef')
    
    

    Output:

    ['abcd1234', 'abcd12', 'abcdef']
    
    
  8. 美元($)元字符

    如果这个元字符出现在搜索字符串的末尾,它就匹配一个模式。

    在下面的例子中,我们使用这个元字符来检查搜索字符串是否以数字结尾。

    CODE:

    re.search(r'[\d]$','aa*5')
    
    

    Output:

    <re.Match object; span=(3, 4), match="5">
    
    

    因为字符串以数字结尾,所以返回一个 match 对象。

  9. 脱字符(^)元字符

    脱字符(^)元字符在字符串的开头查找匹配项。

    在下面的例子中,我们检查搜索字符串是否以空格开头。

    CODE:

    re.search(r'^[\s]','   a bird')
    
    

    Output:

    <re.Match object; span=(0, 1), match=' '>
    
    

延伸阅读:了解更多元字符: https://docs.python.org/3.4/library/re.html#regular-expression-syntax

现在让我们讨论另一个库,Sympy,它用于解决各种基于数学的问题。

使用 Sympy 解决数学问题

SymPy 是 Python 中的一个库,可用于解决各种数学问题。我们首先看一下如何在代数中使用辛函数——解方程和分解表达式。在这之后,我们将介绍集合论和微积分中的一些应用。

可以使用以下语句导入 SymPy 模块。

代码:

import sympy

如果您还没有安装 sympy 模块,请进入 Anaconda 提示符并输入以下命令:

pip install sympy

现在让我们用这个模块来解决各种数学问题,从表达式的因式分解开始。

代数表达式的因式分解

表达式的因式分解包括将表达式分解成更简单的表达式或因子。将这些因素相乘,我们得到了原始表达式。

举个例子,一个代数表达式,像x2y2,可以因式分解为:(x-y)*(x+y)。

SymPy 为我们提供了分解表达式和扩展表达式的功能。

一个代数表达式包含在 SymPy 中被表示为“符号”的变量。在应用 SymPy 函数之前,Python 中的一个变量必须被转换成一个 symbol 对象,该对象是使用 symbols 类(用于定义多个符号)或 Symbol 类(用于定义单个符号)创建的。然后,我们导入因子扩展函数,然后将我们需要进行因子分解或扩展的表达式作为参数传递给这些函数,如下所示。

代码:

#importing the symbol classes
from sympy import symbols,Symbol
#defining the symbol objects
x,y=symbols('x,y')
a=Symbol('a')
#importing the functions
from sympy import factor,expand
#factorizing an expression
factorized_expr=factor(x**2-y**2)
#expanding an expression
expanded_expr=expand((x-y)**3)
print("After factorizing x**2-y**2:",factorized_expr)
print("After expanding (x-y)**3:",expanded_expr)

输出:

After factorizing x**2-y**2: (x - y)*(x + y)
After expanding,(x-y)**3: x**3 - 3*x**2*y + 3*x*y**2 - y**3

解代数方程(一个变量)

代数方程包含一个表达式,带有一系列等于零的项。现在让我们使用 SymPy 中的求解函数来求解方程x25x+6 = 0。

我们从 SymPy 库中导入 solve 函数,并将我们想要求解的方程作为参数传递给这个函数,如下所示。 dict 参数以结构化格式产生输出,但是包含这个参数是可选的。

代码:

#importing the solve function
from sympy import solve
exp=x**2-5*x+6
#using the solve function to solve an equation
solve(exp,dict=True)

输出:

[{x: 2}, {x: 3}]

解联立方程(两个变量)

solve 函数也可以用来同时求解两个方程,如下面的代码块所示。

代码:

from sympy import symbols,solve
x,y=symbols('x,y')
exp1=2*x-y+4
exp2=3*x+2*y-1
solve((exp1,exp2),dict=True)

输出:

[{x: -1, y: 2}]

延伸阅读:查看更多关于求解函数的信息:

https://docs.sympy.org/latest/modules/solvers/solvers.html#algebraic-equations

求解用户输入的表达式

我们可以让用户使用 input 函数输入表达式,而不是定义表达式。问题是用户输入的内容被当作字符串处理,而 SymPy 函数无法处理这样的输入。

函数可以用来将任何表达式转换成与 SymPy 兼容的类型。注意,当输入时,用户必须输入数学运算符,如、**,等等。例如,如果表达式是 2x+3,用户在输入时不能跳过星号。如果用户输入 2x+3,将会产生一个错误。下面的代码块中提供了一个代码示例来演示 sympify 函数。

代码:

from sympy import sympify,solve
expn=input("Input an expression:")
symp_expn=sympify(expn)
solve(symp_expn,dict=True)

输出:

Input an expression:x**2-9
[{x: -3}, {x: 3}]

图解求解联立方程

代数方程也可以用图解法求解。如果把方程画在图上,两条线的交点代表解。

来自 sympy.plotting 模块的绘图函数可用于绘制方程,两个表达式作为参数传递给该函数。

代码:

from sympy.plotting import plot
%matplotlib inline
plot(x+4,3*x)
solve((x+4-y,3*x-y),dict=True)

输出(如图 3-1 所示)。

img/498042_1_En_3_Fig1_HTML.jpg

图 3-1

用图解联立方程

创建和操作集合

集合是唯一元素的集合,有许多操作可以应用于集合。集合用文氏图表示,文氏图描述两个或多个集合之间的关系。

SymPy 为我们提供了创建和操作集合的功能。

首先,您需要从 SymPy 包中导入 FiniteSet 类来处理集合。

代码:

from sympy import FiniteSet

现在,声明这个类的一个对象来创建一个集合,并使用您想要的集合中的数字来初始化它。

代码:

s=FiniteSet(1,2,3)

输出:

{1,2,3}

我们也可以从列表中创建一个集合,如下面的语句所示。

代码:

l=[1,2,3]
s=FiniteSet(*l)

集合的并与交

两个集合的并集是两个集合中所有不同元素的列表,而两个集合的交集包括两个集合的公共元素。

SymPy 为我们提供了一种使用并集交集函数计算两个集合的并集和交集的方法。

我们使用 FiniteSet 类创建集合,然后对它们应用联合相交函数,如下所示。

代码:

s1=FiniteSet(1,2,3)
s2=FiniteSet(2,3,4)
union_set=s1.union(s2)
intersect_set=s1.intersect(s2)
print("Union of the sets is:",union_set)
print("Intersection of the sets is:",intersect_set)

输出:

Union of the sets is: {1, 2, 3, 4}
Intersection of the sets is: {2, 3}

寻找一个事件的概率

事件的概率是事件发生的可能性,用数字定义。

使用集合来定义我们的事件和样本空间,我们可以在 SymPy 函数的帮助下解决概率问题。

让我们考虑一个简单的例子,其中我们发现在前十个自然数中找到 3 的倍数的概率。

为了回答这个问题,我们首先将样本空间“s”定义为一个从 1 到 10 的集合。然后,我们定义事件,用字母“a”表示,它是 3 的倍数的出现。然后,我们通过使用 len 函数,用样本空间中的元素数量来定义该事件中的元素数量,从而找到该事件的概率(‘a’)。这将在下面演示。

代码:

s=FiniteSet(1,2,3,4,5,6,7,8,9,10)
a=FiniteSet(3,6,9)
p=len(a)/len(s)
p

输出:

0.3

延伸阅读:

查看更多可对器械包执行的操作: https://docs.sympy.org/latest/modules/sets.html#compound-sets

与 SymPy: https://docs.sympy.org/latest/modules/sets.html#module-sympy.sets.sets 中的集合相关的所有信息

微积分解题

我们将学习如何使用 SymPy 计算一个函数的极限值、导数、定积分和不定积分。

函数的极限

函数的极限值 f(x)是当 x 接近某一特定值时的函数值。

例如,如果我们取函数 1/x,我们看到随着 x 的增加,1/x 的值继续减少。当 x 接近一个无限大的值时,1/x 变得更接近 0。使用 SymPy 函数- limit 计算极限值,如下所示。

代码:

from sympy import limit,Symbol
x=Symbol('x')
limit(1/x,x,0)

输出:

函数的导数

函数的导数定义了该函数相对于独立变量的变化率。如果我们以距离为函数,以时间为自变量,这个函数的导数就是这个函数对时间的变化率,也就是速度。

SymPy 有一个函数, diff ,以表达式(要计算其导数)和自变量为自变量,返回表达式的导数。

代码:

from sympy import Symbol,diff
x=Symbol('x')
#defining the expression to be differentiated
expr=x**2-4
#applying the diff function to this expression
d=diff(expr,x)
d

输出:

2𝑥

```py

#### 函数的积分

函数的积分也叫做反导数。一个函数对两点的定积分,比如说“p”和“q”,就是曲线下两个极限之间的面积。对于不定积分,这些极限是没有定义的。

在 SymPy 中,可以使用 *integrate* 函数计算积分。

让我们来计算上一个例子中看到的函数的微分(2x)的不定积分。

代码:

from sympy import integrate

applying the integrate function

integrate(d,x)


输出:

𝑥2


让我们使用积分函数来计算上面输出的定积分。integrate 函数接受的参数包括极限值 1 和 4(作为一个元组),以及变量(符号)“x”。

代码:

integrate(d,(x,1,4))


输出:

15


延伸阅读:查看更多关于微分、积分和计算极限的函数: [`https://docs.sympy.org/latest/tutorial/calculus.html`](https://docs.sympy.org/latest/tutorial/calculus.html)

## 摘要

1.  正则表达式是文字和元字符的组合,有多种应用。

2.  正则表达式可用于搜索和替换单词,定位系统中的文件,以及 web 爬行或抓取程序。它还可以应用于数据争论和清理操作,验证用户在电子邮件和 HTML 表单中的输入,以及搜索引擎。

3.  在 Python 中, *re* 模块提供了对正则表达式的支持。Python 中正则表达式匹配常用的函数有: *findall* 、 *search* 、 *match* 、 *split* 和 *sub* 。

4.  元字符是正则表达式中具有特殊意义的字符。每个元字符都有特定的用途。

5.  字符类(以反斜杠符号开头)用于匹配预定义的字符集,如数字、字母数字字符、空白字符等。

6.  Sympy 是一个用于解决数学问题的库。Sympy 中使用的基本构建块称为“符号”,它代表一个变量。我们可以使用 Sympy 库的函数来分解或展开表达式,解方程,微分或积分函数,以及解决涉及集合的问题。

在下一章中,我们将学习另一个 Python 模块 NumPy,它用于创建数组、计算统计聚集度量和执行计算。NumPy 模块也构成了 Pandas 的主干,这是一个用于数据争论和分析的流行库,我们将在第六章中详细讨论。

## 复习练习

**问题 1**

选择不正确的陈述:

1.  即使在集合中使用,元字符也被视为元字符

2.  的。(点/点)元字符用于匹配除换行符之外的任何(单个)字符

3.  正则表达式不区分大小写

4.  默认情况下,正则表达式只返回找到的第一个匹配项

5.  以上都不是

**问题 2**

解释正则表达式的一些用例。

**问题 3**

对一个元字符进行转义的目的是什么,为此使用了哪个字符?

**问题 4**

以下语句的输出是什么?

re.findall('bond\d{1,3}','bond07 bond007 Bond 07')


**问题 5**

将下列元字符与其功能配对:

<colgroup><col class="tcol1 align-left"> <col class="tcol2 align-left"></colgroup> 
|     1\. + | a.匹配零个或一个字符 |
| 2\. * | b.匹配一个或多个字符 |
|     3\. ? | c.匹配字符集 |
|     4\. [ ] | d.匹配搜索字符串末尾的字符 |
| 5\. $ | e.匹配零个或多个字符 |
|     6\. { } | f.指定时间间隔 |

**问题 6**

将以下元字符(用于字符类)与其功能匹配:

<colgroup><col class="tcol1 align-left"> <col class="tcol2 align-left"></colgroup> 
| 1.\d | a.匹配单词的开头或结尾 |
| 2.\D | b.匹配除空白字符以外的任何字符 |
| 3.\S | c.匹配非数字 |
| 4.\w | d.匹配数字 |
| 5.\b | e.匹配字母数字字符 |

**问题 7**

编写一个程序,要求用户输入密码并进行验证。密码应满足以下要求:

*   长度至少应为六个字符

*   至少包含一个大写字母、一个小写字母、一个特殊字符和一个数字

**问题 8**

考虑两个表达式 y=x**2-9 和 y=3*x-11。

使用 SymPy 函数解决以下问题:

*   对表达式 x**2-9 进行因式分解,并列出其因式

*   解这两个方程

*   画出这两个方程,并用图表显示其解

*   对于 x=1,求表达式 x**2-9 的微分

*   求表达式 3*x-11 在点 x=0 和 x=1 之间的定积分

**答案**

**问题 1**

不正确的选项是选项 1 和 3。

选项 1 是不正确的,因为当元字符在集合中使用时,它不被认为是元字符,而是采用其字面意义。

选项 3 不正确,因为正则表达式区分大小写(“hat”与“HAT”不同)。

其他选项都是正确的。

**问题 2**

正则表达式的一些用例包括

1.  HTML 表单中的用户输入验证。正则表达式可用于检查用户输入,并确保输入符合表单中各个字段的要求。

2.  Web 爬行和 web 抓取:正则表达式通常用于从网站搜索一般信息(爬行)和从网站提取某些类型的文本或数据(抓取),例如电话号码和电子邮件地址。

3.  在您的操作系统上定位文件:使用正则表达式,您可以在您的系统上搜索文件名具有相同扩展名或遵循某种其他模式的文件。

**问题 3**

我们对元字符进行转义,以便在字面上使用它。反斜杠字符(\)符号位于要转义的元字符之前。例如,符号“*”在正则表达式中有特殊的含义。如果你想用这个字符它没有特殊的含义,你需要用\*

**问题 4**

输出

['bond07', 'bond007']


**问题 5**

1-b; 2-e; 3-a; 4-d; 5-c; 6-f


**问题 6**

1-d; 2-c; 3-b; 4-e; 5-a


**问题 7**

代码:

import re
special_characters=['$','#','@','&','^','*']
while True:
s=input("Enter your password")
if len(s)<6:
print("Enter at least 6 characters in your password")
else:
if re.search(r'\d',s) is None:
print("Your password should contain at least 1 digit")
elif re.search(r'[A-Z]',s) is None:
print("Your password should contain at least 1 uppercase letter")
elif re.search(r'[a-z]',s) is None:
print("Your password should contain at least 1 lowercase letter")
elif not any(char in special_characters for char in s):
print("Your password should contain at least 1 special character")
else:
print("The password you entered meets our requirements")
break


**问题 8**

代码:

from sympy import Symbol,symbols,factor,solve,diff,integrate,plot

creating symbols

x,y=symbols('x,y')
y=x**2-9
y=3*x-11

factorizing the expression

factor(x**2-9)

solving two equations

solve((x**2-9-y,3*x-11-y),dict=True)

plotting the equations to find the solution

%matplotlib inline
plot(x**2-9-y,3*x-11-y)

differentiating at a particular point

diff(x**2-9,x).subs({x:1})

finding the integral between two points

integrate(3*x-11,(x,0,1))


# 四、描述性数据分析基础

在前面的章节中,您已经了解了 Python 语言——语法、函数、条件语句、数据类型和不同类型的容器。您还回顾了更高级的概念,如正则表达式、文件处理以及用 Python 解决数学问题。我们的焦点现在转向这本书的核心,描述性数据分析(也称为探索性数据分析)。

在描述性数据分析中,我们借助总结、聚合和可视化等方法来分析过去的数据,以得出有意义的见解。相比之下,当我们进行预测分析时,我们试图使用各种建模技术对未来进行预测或预报。

在本章中,我们将了解各种类型的数据、如何对数据进行分类、根据数据类别执行哪些操作,以及描述性数据分析流程的工作流程。

## 描述性数据分析-步骤

图 4-1 逐步说明了描述性数据分析所遵循的方法。

![img/498042_1_En_4_Fig1_HTML.png](https://gitee.com/OpenDocCN/vkdoc-ds-zh/raw/master/docs/py-da-tk/img/498042_1_En_4_Fig1_HTML.png)

图 4-1

描述性数据分析的步骤

让我们详细了解这些步骤。

1.  **数据检索**:数据可以以结构化格式(如数据库或电子表格)或非结构化格式(如网页、电子邮件、Word 文档)存储。在考虑了数据的成本和结构等参数之后,我们需要弄清楚如何检索这些数据。像 Pandas 这样的库提供了以各种格式导入数据的功能。

2.  **粗略的数据回顾和问题识别**:在这一步,我们对想要分析的数据形成第一印象。我们旨在了解每个单独的列或功能、数据集中使用的各种缩写和符号的含义、记录或数据代表的内容以及用于数据存储的单位。我们还需要提出正确的问题,并在进入分析的本质之前弄清楚我们需要做什么。这些问题可能包括以下内容:哪些是与分析相关的功能,各个列中是否有增加或减少的趋势,我们是否看到任何丢失的值,我们是否正在尝试开发预测并预测一个功能,等等。

3.  **数据争论**:这一步是数据分析的关键,也是最耗时的活动,数据分析师和科学家大约 80%的时间都花在这上面。

    由于以下任何原因,原始形式的数据通常不适合于分析:存在缺失和冗余值、异常值、不正确的数据类型、存在无关数据、使用了一个以上的测量单位、数据分散在不同的源中,以及列未被正确识别。

    数据角力或 munging 是转换原始数据以使其适合数学处理和绘制图表的过程。它包括删除或替换丢失的值和不完整的条目,去除分号和逗号等填充值,过滤数据,更改数据类型,消除冗余,以及将数据与其他来源合并。

    数据争论包括整理、清理和丰富数据。在数据整理中,我们识别数据集中的变量,并将它们映射到列。我们还沿着右轴组织数据,并确保行包含观察值而不是特征。将数据转换成整洁形式的目的是使数据具有便于分析的结构。数据清理包括处理缺失值、不正确的数据类型、异常值和错误输入的数据。在数据丰富中,我们可能会添加来自其他来源的数据,并创建可能有助于我们分析的新列或功能。

4.  **数据探索和可视化**:准备好数据后,下一步是发现数据中的模式,总结关键特征,并理解各种特征之间的关系。有了可视化,你可以实现所有这些,并且清晰地呈现关键的发现。用于可视化的 Python 库包括 Matplotlib、Seaborn 和 Pandas。

5.  **展示和发布我们的分析** : Jupyter 笔记本有两个用途,一是执行我们的代码,二是作为一个平台来提供我们分析的高级摘要。通过添加笔记、标题、注释和图像,您可以美化您的笔记本,使其更适合更广泛的受众。笔记本可以下载成各种格式,比如 PDF,以后可以与他人分享以供审阅。

我们现在继续讨论数据的各种结构和层次。

## 数据结构

我们需要分析的数据可能具有以下任何一种结构,如图 4-2 所示。

![img/498042_1_En_4_Fig2_HTML.png](https://gitee.com/OpenDocCN/vkdoc-ds-zh/raw/master/docs/py-da-tk/img/498042_1_En_4_Fig2_HTML.png)

图 4-2

数据结构

## 将数据分类到不同的级别

大体上有两个层次的数据:连续的和分类的。连续数据可以进一步分为比率和区间,而分类数据可以是名义数据或顺序数据。数据等级如图 4-3 所示。

![img/498042_1_En_4_Fig3_HTML.png](https://gitee.com/OpenDocCN/vkdoc-ds-zh/raw/master/docs/py-da-tk/img/498042_1_En_4_Fig3_HTML.png)

图 4-3

数据级别

以下是一些需要注意的要点:

*   **分类变量的数值**:分类数据不限于非数值。例如,学生的排名可以取 1/2/3 等值,这是一个包含数字值的顺序(分类)变量的示例。然而,这些数字不具有数学意义;例如,寻找平均排名是没有意义的。

*   **真零点** **的意义**:我们已经注意到区间变量没有绝对零点作为参考点,而比率变量有一个有效的零点。绝对零表示没有值。例如,当我们说像身高和体重这样的变量是比率变量时,这意味着这些变量中的任何一个值为 0 都意味着无效或不存在的数据点。对于像温度这样的间隔变量(当以摄氏度或华氏度测量时),值 0 并不意味着没有数据。0 只是温度变量可以采用的值之一。另一方面,当在开尔文温标中测量时,温度是一个比率变量,因为该温标定义了绝对零度。

*   **识别区间变量**:区间变量没有绝对零度作为参考点,但是识别具有这种特征的变量可能并不明显。每当我们谈论一个数字的百分比变化时,它都是相对于它以前的值而言的。例如,通货膨胀或失业的百分比变化是以最后一个时间值作为参考点来计算的。这些是区间数据的实例。区间变量的另一个例子是在标准化考试中获得的分数,如 GRE(研究生入学考试)。最低分 260,最高分 340。评分是相对的,不是从 0 开始。与区间数据,而你可以执行加法和减法运算。不能对值进行除法或乘法运算(比率数据允许的运算)。

## 可视化不同级别的数据

每当需要分析数据时,首先要了解数据是结构化的还是非结构化的。如果数据是非结构化的,那么将它转换成具有行和列的结构化形式,这使得使用像 Pandas 这样的库进行进一步分析更加容易。有了这种格式的数据后,将每个要素或列分为四个数据级别,并相应地执行分析。

请注意,在本章中,我们仅旨在了解如何对数据集中的变量进行分类,并确定适用于每个类别的操作和绘图。第七章解释了可视化数据所需编写的实际代码。

我们看看如何对特征进行分类,并使用著名的 *Titanic* 数据集执行各种操作。数据集可以从这里导入:

[`https://github.com/DataRepo2019/Data-files/blob/master/titanic.csv`](https://github.com/DataRepo2019/Data-files/blob/master/titanic.csv)

数据集的背景信息:1912 年 4 月 15 日<sup>号,英国客轮泰坦尼克号在从南安普顿到纽约的处女航中与冰山相撞后沉没。在 2,224 名乘客中,有 1,500 人死亡,这是一场空前的悲剧。这个数据集描述了乘客的生存状态和关于他们的其他细节,包括他们的阶级、姓名、年龄和亲属数量。</sup>

图 4-4 提供了该数据集的快照。

![img/498042_1_En_4_Fig4_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-ds-zh/raw/master/docs/py-da-tk/img/498042_1_En_4_Fig4_HTML.jpg)

图 4-4

泰坦尼克号数据集

表 4-1 记录了该数据集中根据数据级别分类的要素。

表 4-1

泰坦尼克号数据集——数据级别

<colgroup><col class="tcol1 align-left"> <col class="tcol2 align-left"> <col class="tcol3 align-left"></colgroup> 
| 

数据集中的要素

 | 

它代表了什么

 | 

数据水平

 |
| --- | --- | --- |
| 乘客 Id | 乘客的身份号码 | 名义上的 |
| P 类 | 客运班(1:1 <sup>st</sup> 班;2: 2 <sup>级和</sup>级;3: 3 <sup>rd</sup> class),乘客等级被用作衡量乘客的社会经济地位 | 序数 |
| 幸存 | 存活状态(0:未存活;1:幸存) | 名义上的 |
| 名字 | 乘客姓名 | 名义上的 |
| 兄弟姐妹数 | 船上的兄弟姐妹/配偶人数 | 比例 |
| 票 | 票号 | 名义上的 |
| 小木屋 | 客舱号 | 名义上的 |
| 性 | 乘客的性别 | 名义上的 |
| 年龄 | 年龄 | 比例 |
| 烤 | 船上父母/孩子的数量 | 比例 |
| 票价 | 乘客票价(英镑) | 比例 |
| 从事 | 装运港(C 为瑟堡,Q 为皇后镇,S 为南安普敦) | 名义上的 |

现在让我们来了解一下该数据集中要素分类背后的基本原理。

1.  Nominal variables: Variables like “PassengerId”, “Survived”, “Name”, “Sex”, “Cabin”, and “Embarked” do not have any intrinsic ordering of their values. Note that some of these variables have numeric values, but these values are finite in number. We cannot perform an arithmetic operation on these values like addition, subtraction, multiplication, or division. One operation that is common with nominal variables is counting. A commonly used method in Pandas, *value_counts* (discussed in the next chapter), is used to determine the number of values per each unique category of the nominal variable. We can also find the mode (the most frequently occurring value). The bar graph is frequently used to visualize nominal data (pie charts can also be used), as shown in Figure 4-5.

    ![img/498042_1_En_4_Fig5_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-ds-zh/raw/master/docs/py-da-tk/img/498042_1_En_4_Fig5_HTML.jpg)

    图 4-5

    显示每个类别计数的条形图

2.  顺序变量:“Pclass”(或乘客等级)是一个顺序变量,因为它的值遵循一个顺序。值 1 相当于一等,2 相当于二等,依此类推。这些阶级价值观表明了社会经济地位。

    我们可以找出中间值和百分位数。我们还可以计算每个类别中值的数量,计算模式,并使用条形图和饼图等图表,就像我们对名义变量所做的那样。

    In Figure 4-6, we have used a pie chart for the ordinal variable “Pclass”.

    ![img/498042_1_En_4_Fig6_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-ds-zh/raw/master/docs/py-da-tk/img/498042_1_En_4_Fig6_HTML.jpg)

    图 4-6

    显示每个类别的百分比分布的饼图

3.  比率数据:“年龄”和“费用”变量是比率数据的例子,以零值作为参考点。有了这种类型的数据,我们可以进行广泛的数学运算。

    For example, we can add all the fares and divide it by the total number of passengers to find the mean. We can also find out the standard deviation. A histogram, as shown in Figure 4-7, can be used to visualize this kind of continuous data to understand the distribution.

    ![img/498042_1_En_4_Fig7_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-ds-zh/raw/master/docs/py-da-tk/img/498042_1_En_4_Fig7_HTML.jpg)

    图 4-7

    显示比率变量分布的直方图

在前面的图中,我们查看了用于绘制单个分类变量或连续变量的图表。在下一节中,我们了解当我们有一个以上的变量或变量组合属于不同的规模或水平时,应使用哪些图表。

### 绘制混合数据

在本节中,我们将考虑三个场景,每个场景都有两个变量,它们可能属于也可能不属于同一级别,并讨论每个场景使用哪个图(使用相同的 *Titanic* 数据集)。

1.  One categorical and one continuous variable: A box plot shows the distribution, symmetry, and outliers for a continuous variable. A box plot can also show the continuous variable against a categorical variable. In Figure 4-8, the distribution of ‘Age’ (a ratio variable) for each value of the nominal variable – ‘Survived’ (0 is the value for passengers who did not survive and 1 is the value for those who did).

    ![img/498042_1_En_4_Fig8_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-ds-zh/raw/master/docs/py-da-tk/img/498042_1_En_4_Fig8_HTML.jpg)

    图 4-8

    箱形图,显示不同类别的年龄分布

2.  Both continuous variables: Scatter plots are used to depict the relationship between two continuous variables. In Figure 4-9, we plot two ratio variables, ‘Age’ and ‘Fare’, on the x and y axes to produce a scatter plot.

    ![img/498042_1_En_4_Fig9_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-ds-zh/raw/master/docs/py-da-tk/img/498042_1_En_4_Fig9_HTML.jpg)

    图 4-9

    散点图

3.  Both categorical variables: Using a clustered bar chart (Figure 4-10), you can combine two categorical variables with the bars depicted side by side to represent every combination of values for the two variables.

    ![img/498042_1_En_4_Fig10_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-ds-zh/raw/master/docs/py-da-tk/img/498042_1_En_4_Fig10_HTML.jpg)

    图 4-10

    簇状条形图

我们也可以使用堆积条形图来绘制两个分类变量。考虑下面的堆积条形图,如图 4-11 所示,绘制了两个分类变量——“Pclass”和“Survived”。

![img/498042_1_En_4_Fig11_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-ds-zh/raw/master/docs/py-da-tk/img/498042_1_En_4_Fig11_HTML.jpg)

图 4-11

堆积条形图

总的来说,当您想要显示某个分类变量的不同值之间的连续变量时,您可以使用散点图来显示两个连续变量,使用堆积或簇状条形图来显示两个分类变量,以及使用箱形图。

## 摘要

1.  描述性数据分析是一个五步过程,使用过去的数据,遵循逐步的方法。这个过程的核心——数据争论——包括处理缺失值和其他异常。它还处理重组、合并和转换。

2.  数据可以根据其结构(结构化、非结构化或半结构化)或其包含的值的类型(分类或连续)进行分类。

3.  分类数据可以分为名义数据和顺序数据(取决于值是否可以排序)。连续数据可以是比率或区间类型(取决于数据是否以 0 作为绝对参考点)。

4.  可以使用的数学运算和图形绘制的种类因数据水平而异。

现在你已经对描述性数据分析过程有了一个高层次的了解,我们在下一章进入数据分析的本质。在下一章中,我们将讨论如何为我们在数据争论和准备中执行的各种任务编写代码,这一章将介绍 Pandas 库。

## 复习练习

**问题 1**

根据数据类型对下列变量进行分类。

*   pH 标度

*   语言能力

*   李克特量表(用于调查)

*   工作经验

*   一天中的时间

*   社会保险号

*   距离

*   出生年

**问题 2**

按照数据分析过程中出现的顺序安排以下五个步骤。

1.  形象化

2.  分析的发布和展示

3.  导入数据

4.  数据争论

5.  问题陈述公式

**问题 3**

对于以下每个操作或统计测量,列出兼容的数据类型。

*   分开

*   添加

*   增加

*   减法

*   平均

*   中位数

*   方式

*   标准偏差

*   范围

**问题 4**

对于以下每一项,列出兼容的数据类型。

*   条形图

*   直方图

*   饼图

*   散点图

*   堆积条形图

**答案**

**问题 1**

*   pH 值刻度:间隔

pH 值刻度没有绝对零点。虽然这些值可以比较,但我们无法计算比率。

*   语言能力:序数

    一门语言的熟练程度有不同的等级,如“初级”、“中级”和“高级”,这些都是有序的,因此属于顺序等级。

*   李克特量表(用于调查):序数。

    李克特量表常用于调查,其值有“不满意”、“满意”和“非常满意”。这些值形成了一个逻辑顺序,因此任何代表李克特量表的变量都是序数变量。

*   工作经验:比率

    因为这个变量有一个绝对零点,并且可以进行算术运算,包括比率的计算,所以这个变量是一个比率变量。

*   一天中的时间:间隔

    时间(12 小时制)没有绝对的零点。我们可以计算两个时间点之间的差异,但不能计算比率。

*   社会安全号码:名义上的

    像社会保险号这样的标识符的值是没有顺序的,不适合数学运算。

*   距离:比率

    参考点为 0,值可以加、减、乘、除,距离是一个比率变量。

*   出生年份:间隔

    这样的变量没有绝对的零点。你可以计算两年之间的差异,但我们无法找出比率。

**问题 2**

正确的顺序是 3,5,4,1,2

**问题 3**

*   分部:比率数据

*   加法:比率数据、区间数据

*   乘法:比率数据

*   减法:区间数据、比率数据

*   均值:比率数据、区间数据

*   中位数:序数数据,比率数据,区间数据

*   模式:所有四个级别的数据(比率、区间、标称和序数)

*   标准差:比率和区间数据

*   范围:比率和区间数据

**问题 4**

*   箱线图:序数、比率、间隔

*   直方图:比率,间隔

*   饼图:名义值,序数

*   散点图:比率,间隔

*   堆积条形图:名义值,序数

# 五、使用 NumPy 数组

NumPy 或 Numerical Python 是一个基于 Python 的库,用于数学计算和处理数组。Python 不支持一维以上的数据结构,像列表、元组和字典这样的容器是一维的。Python 中内置的数据类型和容器不能被重构为一个以上的维度,也不适合复杂的计算。这些缺点限制了分析数据和构建模型时所涉及的一些任务,这使得数组成为一种重要的数据结构。

NumPy 数组可以被重新整形,并利用向量化的原理(其中应用于数组的操作反映在其所有元素上)。

在前一章,我们看了描述性数据分析中使用的基本概念。NumPy 是我们在数据分析中执行的许多任务不可或缺的一部分,是 Pandas 中使用的许多函数和数据类型的主干。在本章中,我们了解了如何使用各种方法创建 NumPy 数组,组合数组,对数组进行切片、整形和执行计算。

## 熟悉数组和 NumPy 函数

在这里,我们看看创建和组合数组的各种方法,以及常用的 NumPy 函数。

**导入 NumPy 包**

必须先导入 NumPy 包,然后才能使用它的函数,如下所示。NumPy 的简写符号或别名是 *np* 。

代码:

```py
import numpy as np

如果您尚未安装 NumPy,请转到 Anaconda 提示符并输入以下命令:

pip install numpy

创建数组

NumPy 中的基本单位是一个数组。在表 5-1 中,我们来看看创建数组的各种方法。

表 5-1

创建 NumPy 数组的方法

|

方法

|

例子

|
| --- | --- |
| 从列表中创建数组 | 函数用来从一个列表中创建一个一维或多维数组。代码:np.array([[1,2,3],[4,5,6]])输出:array([[1, 2, 3],``[4, 5, 6]]) |
| 从一个范围创建数组 | 函数用于创建一个整数范围。代码:np.arange(0,9)``#Alternate syntax:``np.arange(9)``#Generates 9 equally spaced integers starting from 0输出:array([0, 1, 2, 3, 4, 5, 6, 7, 8]) |
| 创建一个等距数字数组 | 函数在两个极限之间创建给定数量的等距值。代码:np.linspace(1,6,5)``# This generates five equally spaced values between 1 and 6输出:array([1.  , 2.25, 3.5 , 4.75, 6.  ]) |
| 创建一个零数组 | 函数创建一个给定行数和列数的数组,数组中只有一个值“0”。代码:np.zeros((4,2))``#Creates a 4*2 array with all values as 0输出:array([[0., 0.],``[0., 0.],``[0., 0.],``[0., 0.]]) |
| 创建一个 1 的数组 | np.ones 函数类似于 np.zeros 函数,不同之处在于在整个数组中重复的值是“1”。代码:np.ones((2,3))``#creates a 2*3 array with all values as 1输出:array([[1., 1., 1.],``[1., 1., 1.]]) |
| 创建一个数组,其中给定的值在整个过程中重复 | 函数使用用户指定的值创建一个数组。代码:np.full((2,2),3)``#Creates a 2*2 array with all values as 3输出:array([[3, 3],``[3, 3]]) |
| 创建空数组 | 函数 np.empty 生成一个数组,没有任何特定的初始值(数组是随机初始化的)。代码:np.empty((2,2))``#creates a 2*2 array filled with random values输出:array([[1.31456805e-311, 9.34839993e+025],``[2.15196058e-013, 2.00166813e-090]]) |
| 从重复列表创建数组 | 函数从一个重复给定次数的列表中创建一个数组。代码:np.repeat([1,2,3],3)``#Will repeat each value in the list 3 times输出:array([1, 1, 1, 2, 2, 2, 3, 3, 3]) |
| 创建随机整数数组 | randint 函数(来自 np.random 模块)生成一个包含随机数的数组。代码:np.random.randint(1,100,5)``#Will generate an array with 5 random numbers between 1 and 100输出:array([34, 69, 67,  3, 96]) |

需要注意的一点是,数组是同构的数据结构,不像容器(像列表、元组、字典);也就是说,数组应该包含相同数据类型的项。例如,我们不能拥有一个同时包含整数、字符串和浮点(十进制)值的数组。虽然用不同数据类型的项定义 NumPy 数组不会在编写代码时导致错误,但应该避免这种情况。

现在我们已经了解了定义数组的各种方法,我们来看看可以对它们执行的操作,从数组的整形开始。

重塑数组

重塑数组是改变数组维数的过程。NumPy 方法“reshape”很重要,通常用于将一维数组转换为多维数组。

考虑一个包含十个元素的简单一维数组,如下面的语句所示。

代码:

x=np.arange(0,10)

输出:

array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

我们可以将一维数组“x”改造成一个五行两列的二维数组:

代码:

x.reshape(5,2)

输出:

array([[0, 1],
       [2, 3],
       [4, 5],
       [6, 7],
       [8, 9]])

作为另一个示例,考虑以下数组:

代码:

x=np.arange(0,12)
x

输出:

array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])

现在,应用 reshape 方法创建两个子数组——每个子数组有三行两列:

代码:

x=np.arange(0,12).reshape(2,3,2)
x

输出:

array([[[ 0,  1],
        [ 2,  3],
        [ 4,  5]],

       [[ 6,  7],
        [ 8,  9],
        [10, 11]]])

经过整形的数组的维数乘积应该等于原始数组中的元素数。在这种情况下,数组(2,3,2)的维数乘以等于 12,即数组中元素的数目。如果不满足这个条件,整形就不起作用。

除了 reshape 方法,我们还可以使用 shape 属性来改变数组的形状或维度:

代码:

x.shape=(5,2)
#5 is the number of rows, 2 is the number of columns

请注意, shape 属性对原始数组进行更改,而 reshape 方法不会改变数组。

使用“拆纱”方法可以逆转整形过程:

代码:

x=np.arange(0,12).reshape(2,3,2)
x.ravel()

输出:

array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])

延伸阅读:查看更多关于数组创建的套路: https://numpy.org/doc/stable/reference/routines.array-creation.html#

数组的逻辑结构

用于指定点的位置的笛卡尔坐标系由一个平面组成,该平面具有两条称为“x”和“y”轴的垂直线。使用点的 x 和 y 坐标来指定点的位置。这种用轴来表示不同维度的原理也用在数组中。

一维数组有一个轴(轴=0),因为它有一个维度,如图 5-1 所示。

img/498042_1_En_5_Fig1_HTML.png

图 5-1

一维数组表示

二维数组的轴值为“0”表示行轴,值为“1”表示列轴,如图 5-2 所示。

img/498042_1_En_5_Fig2_HTML.png

图 5-2

二维数组表示形式

三维数组有三个轴,代表三个维度,如图 5-3 所示。

img/498042_1_En_5_Fig3_HTML.png

图 5-3

三维数组表示

扩展逻辑,具有“n”维的阵列具有“n”个轴。

请注意,前面的图表仅代表数组的逻辑结构。当涉及到内存中的存储时,数组中的元素占据连续的位置,而与维数无关。

NumPy 数组的数据类型

type 函数可以用来确定 NumPy 数组的类型:

代码:

type(np.array([1,2,3,4]))

输出:

numpy.ndarray

修改数组

数组的长度是在定义时设置的。让我们考虑下面的数组“a”:

代码:

a=np.array([0,1,2])

前面的代码语句将创建一个长度为 3 的数组。此后,数组长度不可修改。换句话说,我们不能在数组定义后添加新元素。

下面的语句将导致错误,在该语句中,我们尝试向该数组添加第四个元素:

代码:

a[3]=4

输出:

---------------------------------------------------------------------------
IndexErrorTraceback (most recent call last)
<ipython-input-215-94b083a55c38> in <module>
----> 1a[3]=4

IndexError: index 3 is out of bounds for axis 0 with size 3
---------------------------------------------------------------------------

但是,您可以更改现有元素的值。下面的语句就可以了:

代码:

a[0]=2

总之,虽然可以修改数组中现有项目的值,但不能向数组中添加新项目。

现在我们已经看到了如何定义和整形一个数组,我们来看看如何组合数组。

组合数组

组合数组有三种方法:追加、串联和堆叠。

  1. 追加包括将一个数组连接到另一个数组的末尾。 np.append 函数用于追加两个数组。

    CODE:

    x=np.array([[1,2],[3,4]])
    y=np.array([[6,7,8],[9,10,11]])
    np.append(x,y)
    
    

    Output:

    array([ 1,  2,  3,  4,  6,  7,  8,  9, 10, 11])
    
    
  2. 串联包括沿一个轴(垂直或水平)连接数组。函数的作用是连接数组。

    CODE:

    x=np.array([[1,2],[3,4]])
    y=np.array([[6,7],[9,10]])
    np.concatenate((x,y))
    
    

    Output:

    array([[ 1,  2],
           [ 3,  4],
           [ 6,  7],
           [ 9, 10]])
    
    

    默认情况下, concatenate 函数垂直连接数组(沿“0”轴)。如果您希望数组并排连接,则需要添加“轴”参数,其值为“1”:

    CODE:

    np.concatenate((x,y),axis=1)
    
    

    追加函数在内部使用连接函数。

  3. 堆叠:堆叠有垂直和水平两种,如下所述。

    垂直堆叠

    顾名思义,垂直堆叠将阵列一个接一个地堆叠起来。垂直堆叠的阵列的每个子阵列中的元素数量必须相同,垂直堆叠才能工作。 np.vstack 函数用于垂直堆叠。

    CODE:

    x=np.array([[1,2],[3,4]])
    y=np.array([[6,7],[8,9],[10,11]])
    np.vstack((x,y))
    
    

    Output:

    array([[ 1,  2],
           [ 3,  4],
           [ 6,  7],
           [ 8,  9],
           [10, 11]])
    
    

    看看数组“x”和“y”的每个子数组中有两个元素。

    水平堆叠

    水平堆叠并排堆叠阵列。对于水平堆叠的每个阵列,子阵列的数量需要相同。 np.hstack 函数用于水平堆叠。

    在下面的例子中,我们在每个数组中都有两个子数组,“x”和“y”。

    CODE:

    x=np.array([[1,2],[3,4]])
    y=np.array([[6,7,8],[9,10,11]])
    np.hstack((x,y))
    
    

    Output:

    array([[ 1,  2,  6,  7,  8],
    [ 3,  4,  9, 10, 11]])
    
    

在下一节中,我们将研究如何使用逻辑运算符来测试 NumPy 数组中的条件。

条件测试

NumPy 使用逻辑运算符(&、|、~),以及类似于 np.anynp.allnp.where 的函数来检查条件。返回数组中满足条件的元素(或它们的索引)。

考虑以下阵列:

代码:

x=np.linspace(1,50,10)
x

输出:

array([ 1\.        ,  6.44444444, 11.88888889, 17.33333333, 22.77777778,
       28.22222222, 33.66666667, 39.11111111, 44.55555556, 50\.        ])

让我们检查以下条件,看看哪些元素满足这些条件:

  • 检查所有值是否满足给定的条件:只有当数组中的所有项都满足该条件时, np.all 函数才返回值" True ",如下例所示。

    CODE:

    np.all(x>20)
    #returns True only if all the elements are greater than 20
    
    

    输出:

    False
    
    
  • 检查数组中是否有任何值满足某个条件:如果有任何项满足该条件, np.any 函数返回值" True"。

    CODE:

    np.any(x>20)
    #returns True if any one element in the array is greater than 20
    
    

    输出:

    True
    
    
  • 返回满足条件的项目的索引: np.where 函数返回满足给定条件的数组中的值的索引。

    CODE:

    np.where(x<10)
    #returns the index of elements that are less than 10
    
    

    输出:

    (array([0, 1], dtype=int64),)
    
    

    np.where 函数对于有选择地检索或过滤数组中的值也很有用。例如,我们可以使用下面的代码语句检索那些满足条件“x < 10”的项目:

    CODE:

    x[np.where(x<10)]
    
    

    Output:

    array([1\.        , 6.44444444])
    
    
  • 检查多个条件:

    NumPy uses the following Boolean operators to combine conditions:

    • & operator(相当于 Python 中的运算符):当所有条件都满足时返回 True:

      CODE:

      x[(x>10) & (x<50)]
      #Returns all items that have a value greater than 10 and less than 50
      
      

      Output:

      array([11.88888889, 17.33333333, 22.77777778, 28.22222222, 33.66666667,
      39.11111111, 44.55555556])
      
      
    • |操作符(相当于 Python 中的操作符):当一组给定条件中的任意一个条件得到满足时,返回 True。

      CODE:

      x[(x>10) | (x<5)]
      #Returns all items that have a value greater than 10 or less than 5
      
      

      Output:

      array([ 1\.        , 11.88888889, 17.33333333, 22.77777778, 28.22222222,
              33.66666667, 39.11111111, 44.55555556, 50\.        ])
      
      
    • ~运算符(相当于 Python 中的而非运算符),用于对条件求反。

      CODE:

      x[~(x<8)]
      #Returns all items greater than 8
      
      

      Output:

      array([11.88888889, 17.33333333, 22.77777778, 28.22222222, 33.66666667,
      39.11111111, 44.55555556, 50\.        ])
      
      

我们现在继续讨论 NumPy 中的其他一些重要概念,比如广播和向量化。我们还讨论了算术运算符在 NumPy 数组中的使用。

广播、向量化和算术运算

广播

当我们说两个数组可以一起广播时,这意味着它们的维数对于对它们执行算术运算是兼容的。只要遵循广播规则,就可以使用算术运算符组合数组,下面将对此进行解释。

  1. 两个阵列具有相同的尺寸。

    在本例中,两个数组的尺寸都是 2*6。

    CODE:

    x=np.arange(0,12).reshape(2,6)
    y=np.arange(5,17).reshape(2,6)
    x*y
    
    

    Output:

    array([[  0,   6,  14,  24,  36,  50],
    [ 66,  84, 104, 126, 150, 176]])
    
    
  2. 其中一个数组是单元素数组。

    在本例中,第二个数组只有一个元素。

    CODE :

    x=np.arange(0,12).reshape(2,6)
    y=np.array([1])
    x-y
    
    

    Output:

    array([[-1,  0,  1,  2,  3,  4],
    [ 5,  6,  7,  8,  9, 10]])
    
    
  3. 数组和标量(单个值)组合在一起。

    在本例中,变量 y 在运算中用作标量值。

    CODE:

    x=np.arange(0,12).reshape(2,6)
    y=2
    x/y
    
    

    Output:

    array([[0\. , 0.5, 1\. , 1.5, 2\. , 2.5],
    [3\. , 3.5, 4\. , 4.5, 5\. , 5.5]])
    
    

我们可以使用算术运算符(+/-/*和/)或函数( np.addnp.subtractnp.multiplynp.divide )对数组进行加、减、乘和除操作

代码:

np.add(x,y)
#Or
x+y

输出:

array([[ 6,  8],
       [11, 13]])

同样,你可以用 np.subtract (或——运算符)做减法,用 np.multiply (或*运算符)做乘法,用 np.divide (或/运算符)做除法。

延伸阅读:查看更多关于阵列广播: https://numpy.org/doc/stable/user/basics.broadcasting.html

矢量化

使用向量化的原理,您还可以方便地对数组中的每个对象应用算术运算符,而不是遍历元素,这是您对列表等容器中的项目应用运算时会做的事情。

代码:

x=np.array([2,4,6,8])
x/2
#divides each element by 2

输出:

array([1., 2., 3., 4.])

点积

我们可以得到两个数组的点积,这和两个数组相乘是不一样的。将两个数组相乘得到元素的乘积,而两个数组的点积计算元素的内积。

如果我们取两个数组,

|PQ|
|RS|
and
|UV|
|WX|

点积由下式给出

|PQ| . |UV| = |P*U+Q*VP*V+Q*X|
|R S|   |WX|   |R*U+S*WR*V+S*X|

将数组相乘得到以下结果:

|PQ| * |UV| = |P*U Q*V|
|R S|   |WX|   |R*WS*X|

如前所述,数组可以用乘法运算符(*)或 np.multiply 函数相乘。

获得点积的 NumPy 函数是 np打点。

代码:

np.dot(x,y)

输出:

array([[21, 24],
       [47, 54]])

我们也可以把一个数组和一个标量结合起来。

在下一个主题中,我们将讨论如何获取数组的各种属性。

获取数组的属性

使用属性可以找出数组属性,如大小、维度、元素数量和内存使用情况。

考虑以下阵列:

  • 属性给出了数组中元素的数量。

    代码:

    x.size
    
    

    Output:

    10
    
    
  • 属性给出了维度的数量。

    代码:

    x.ndim
    
    

    Output:

    2
    
    
  • 数组占用的内存(总字节数)可以使用 nbytes 属性来计算。

    代码:

    x.nbytes
    
    

    Output:

    40
    
    

    每个元素占用 4 个字节(因为这是一个 int 数组);因此,十个元素占用 40 个字节

  • 这个数组中元素的数据类型可以使用 dtype 属性来计算。

    代码:

    x.dtype
    
    

    Output:

    dtype('int32')
    
    
x=np.arange(0,10).reshape(5,2)

注意数组的类型类型之间的区别。 type 函数给出了容器对象的类型(在本例中,类型是 ndarray ),而 dtype 是一个属性,给出了数组中单个项目的类型。

延伸阅读:了解关于 NumPy 支持的数据类型列表的更多信息:

https://numpy.org/devdocs/user/basics.types.html

转置一个数组

数组的转置是它的镜像。

考虑以下阵列:

代码:

x=np.arange(0,10).reshape(5,2)

有两种转置数组的方法:

  • 我们可以用 np.transpose 的方法。

    代码:

  • 或者,我们可以使用 T 属性来获得转置。

    代码:

np.transpose(x)

x.T

这两种方法给出相同的输出:

array([[0, 2, 4, 6, 8],
[1, 3, 5, 7, 9]])

屏蔽阵列

假设您使用一个 NumPy 数组来存储一个班级学生的考试成绩。虽然你有大多数学生的数据,但也有一些缺失值。在这种情况下,用于存储具有无效或缺失条目的数据的掩码数组非常有用。

可以通过创建“ma.masked_array”类(numpy.ma 模块的一部分)的对象来定义屏蔽数组:

代码:

import numpy.ma as ma
x=ma.masked_array([87,99,100,76,0],[0,0,0,0,1])
#The last element is invalid or masked
x[4]

输出:

Masked

两个数组作为参数传递给 ma.masked_array 类——一个包含数组中各项的值,另一个包含掩码值。掩码值“0”表示相应的项值有效,掩码值“1”表示它缺失或无效。例如,在前面的示例中,值 87、99、100 和 76 是有效的,因为它们具有掩码值“0”。第一个数组(0)中掩码值为“1”的最后一项无效。

掩码值也可以使用掩码属性来定义。

代码:

x=ma.array([87,99,100,76,0])
x.mask=[0,0,0,0,1]

要取消元素屏蔽,请为其赋值:

代码:

x[4]=82

此元素的掩码值变为 1,因为它不再有效。

现在让我们看看如何从数组中创建子集。

切片或选择数据子集

数组切片类似于 Python 中字符串和列表的切片。切片是数据结构(在本例中是数组)的子集,它可以表示一组值或单个值。

考虑以下阵列:

代码:

x=np.arange(0,10).reshape(5,2)

输出:

array([[0, 1],
       [2, 3],
       [4, 5],
       [6, 7],
       [8, 9]])

下面给出了一些切片的例子。

  • 选择第一个子数组[0,1]:

    代码:

    x[0]
    
    

    Output:

    array([0, 1])
    
    
  • 选择第二列:

    CODE:

    x[:,1]
    #This will select all the rows and the 2ndcolumn (has an index of 1)
    
    

    输出:

    array([1, 3, 5, 7, 9])
    
    
  • 选择第四行第一列的元素:

    代码:

    x[3,0]
    
    

    Output:

    6
    
    
  • 我们还可以基于以下条件创建切片:

    代码:

    x[x<5]
    
    

    Output:

    array([0, 1, 2, 3, 4])
    
    

当我们分割一个数组时,原始数组没有被修改(数组的副本被创建)。

现在我们已经学习了如何创建和使用数组,接下来我们将学习 NumPy 的另一个重要应用——使用各种函数计算统计量。

获取描述性统计数据/汇总数据

NumPy 中有一些方法可以简化复杂的计算和确定聚合度量。

让我们找到该数组的集中趋势(平均值、方差、标准偏差)、总和、累积总和以及最大值的度量:

代码:

x=np.arange(0,10).reshape(5,2)
#mean
x.mean()

输出:

4.5

找出差异:

代码:

x.var() #variance

输出:

2.9166666666666665

计算标准偏差:

代码:

x.std()  #standard deviation

输出:

1.707825127659933

计算每列的总和:

代码:

x.sum(axis=0) #calculates the column-wise sum

输出:

array([ 6, 15])

计算累积和:

代码:

x.cumsum()
#calculates the sum of 2 elements at a time and adds this sum to the next element

输出:

array([ 0,  1,  3,  6, 10, 15, 21, 28, 36, 45], dtype=int32)

找出数组中的最大值:

代码:

x.max()

输出:

9

在结束本章之前,让我们了解一下矩阵 NumPy 包支持的另一种数据结构。

矩阵

矩阵是二维数据结构,而数组可以由任意维数组成。

使用 np.matrix 类,我们可以使用以下语法创建一个矩阵对象:

代码:

x=np.matrix([[2,3],[33,3],[4,1]])
#OR
x=np.matrix('2,3;33,3;4,1') #Using semicolons to separate the rows
x

输出:

matrix([[ 2,  3],
        [33,  3],
        [ 4,  1]])

大多数可以应用于数组的函数也可以应用于矩阵。矩阵使用一些算术运算符,使矩阵运算更加直观。例如,我们可以使用*运算符来获得两个矩阵的点积,它复制了函数 np.dot 的功能。

由于矩阵只是数组的一个特例,在 NumPy 的未来版本中可能会被弃用,所以通常最好使用 NumPy 数组。

摘要

  • NumPy 是一个用于数学计算和创建数据结构(称为数组)的库,数组可以包含任意维数。

  • 创建数组有多种方法,也可以改变数组的形状以添加更多的维度或更改现有的维度。

  • 数组支持矢量化,这提供了一种快速直观的方法来对数组的所有元素应用算术运算符。

  • 可以使用简单的 NumPy 函数计算各种统计和聚合度量,如 np.meannp.varnp.std 等等。

复习练习

问题 1

创建以下数组:

array([[[ 1,  2,  3,  4],
        [ 5,  6,  7,  8]],

       [[ 9, 10, 11, 12],
        [13, 14, 15, 16]],

       [[17, 18, 19, 20],
        [21, 22, 23, 24]]])

对前面的数组进行切片以获得以下内容:

  • 第三个子阵列(17,18,19,20,21,22,23,24)中的元素

  • 最后一个元素(24)

  • 第二列中的元素(2,6,10,14,18,22)

  • 对角线上的元素(1,10,19,24)

问题 2

使用适当的 NumPy 函数创建每个数组:

  • 有七个随机数的数组

  • 未初始化的 2*5 数组

  • 一个数组,包含 10 个 1 到 3 之间的等距浮点数

  • 所有值都为 100 的 3*3 数组

问题 3

为以下各项编写简单的代码语句:

  • 创建一个数组来存储前 50 个偶数

  • 计算这些数字的平均值和标准偏差

  • 将此数组重塑为包含两个子数组的数组,每个子数组有五行和五列

  • 计算这个重新成形的数组的维数

问题 4

计算这两种数据结构的点积:

[[ 2,  3],
[33,  3],
[ 4,  1]]

AND

[[ 2,  3, 33],
[ 3,  4,  1]]

使用

  1. 矩阵

  2. 数组

问题 5

第一部分和第二部分中编写的代码有什么不同,输出会有什么不同?

第一部分:

代码:

x=np.array([1,2,3])
x*3

第二部分:

代码:

a=[1,2,3]
a*3

答案

问题 1

  • 第三个子阵列(17,18,19,20,21,22,23,24)中的元素:

    代码:

x=np.arange(1,25).reshape(3,2,4)

  • 最后一个元素(24):

    代码:

x[2]

  • 第二列中的元素(2,6,10,14,18,22):

    代码:

x[2,1,3]

  • 对角线上的元素(1,10,19,24):

    代码:

x[:,:,1]

x[0,0,0],x[1,0,1],x[2,0,2],x[2,1,3]

问题 2

  • 包含七个随机数的数组:

    代码:

  • 未初始化的 2*5 数组:

    代码:

np.random.randn(7)

  • 一个数组,包含 10 个 1 到 3 之间的等距浮点数:

    代码:

np.empty((2,5))

  • 所有值都为 100 的 3*3 数组:

    代码:

np.linspace(1,3,10)

np.full((3,3),100)

问题 3

代码:

#creating the array of first 50 even numbers
x=np.arange(2,101,2)
#calculating the mean
x.mean()
#calculating the standard deviation
x.std()
#reshaping the array
y=x.reshape(2,5,5)
#calculating its new dimensions
y.ndim

问题 4

使用矩阵计算点积需要使用*算术运算符

代码:

x=np.matrix([[2,3],[33,3],[4,1]])
y=np.matrix([[2,3,33],[3,4,1]])
x*y

使用数组计算点积需要使用点方法。

代码:

x=np.array([[2,3],[33,3],[4,1]])
y=np.array([[2,3,33],[3,4,1]])
x.dot(y)

问题 5

产出:

  1. array([3, 6, 9])

    数组支持矢量化,因此*运算符应用于每个元素。

  2. [1, 2, 3, 1, 2, 3, 1, 2, 3]

    对于列表,不支持矢量化,应用*运算符只是重复列表,而不是将元素乘以给定的数字。需要一个“for”循环来对每个项目应用算术运算符。

六、使用 Pandas 准备你的数据

随着互联网、社交网络、移动设备和大数据的爆炸式增长,可用的数据量非常庞大。管理和分析这些数据以得出有意义的推论可以推动决策制定、提高生产率和降低成本。在前一章中,你学习了 NumPy——帮助我们处理数组和执行计算的库,也是我们在本章中讨论的 Pandas 库的主干。Pandas 是用于处理数据的 Python 库,其优势在于它是一个强大的工具,具有许多操纵数据的能力。

Python 作为首选编程语言的日益流行与其在数据科学领域的广泛应用密切相关。2019 年在 Python 开发者中进行的一项调查发现,NumPy 和 Pandas 是最受欢迎的数据科学框架(来源: https://www.jetbrains.com/lp/python-developers-survey-2019/ )。

在这一章中,我们将学习 Pandas 的构造块(序列、数据帧和索引),并了解该库中用于整理、清理、合并和聚合 Pandas 中的数据的各种函数。这一章比你到目前为止读过的其他章节更复杂,因为我们涵盖了广泛的主题,这些主题将帮助你发展准备数据所必需的技能。

Pandas 一瞥

韦斯·麦金尼在 2008 年开发了 Pandas 图书馆。Pandas 这个名字来自计量经济学中用于分析时间序列数据的术语“面板数据”。Pandas 有许多特性,如下所列,这些特性使它成为数据争论和分析的流行工具。

  1. Pandas 提供了标记数据或索引的特性,这加快了数据的检索。

  2. 输入和输出支持:Pandas 提供了从不同文件格式读取数据的选项,如 JSON (JavaScript 对象表示法)、CSV(逗号分隔值)、Excel 和 HDF5(分层数据格式版本 5)。它还可以用于将数据写入数据库、web 服务等。

  3. 分析所需的大部分数据并不包含在一个单一的源中,我们经常需要组合数据集来整合分析所需的数据。Pandas 再一次用定制的功能来拯救数据。

  4. 速度和增强的性能:Pandas 库基于 Cython,它结合了 Python 的便利性和易用性以及 C 语言的速度。Cython 有助于优化性能和降低开销。

  5. 数据可视化:为了从数据中获得洞察力,并使其能够呈现给观众,使用可视化手段查看数据是至关重要的,Pandas 提供了许多内置的可视化工具,使用 Matplotlib 作为基础库。

  6. 对其他库的支持:Pandas 可以与其他库顺利集成,如 Numpy、Matplotlib、Scipy 和 Scikit-learn。因此,我们可以结合数据操作执行其他任务,如数值计算、可视化、统计分析和机器学习。

  7. 分组:Pandas 提供了对拆分-应用-组合方法的支持,通过这种方法,我们可以将数据分组,对它们应用独立的函数,并组合结果。

  8. 处理缺失数据、重复数据和填充字符:数据通常有缺失值、重复数据、空格、特殊字符(如$、&)等,这些可能需要删除或替换。有了 Pandas 提供的功能,您可以轻松处理这种异常情况。

  9. 数学运算:许多数字运算和计算可以在 Pandas 中执行,NumPy 在后端用于此目的。

技术要求

本章所需的库和外部文件将在下面详细介绍。

安装库

如果您还没有安装 Pandas,请进入 Anaconda 提示符并输入以下命令。

>>>pip install pandas

一旦安装了 Pandas 库,您需要在使用它的功能之前导入它。在您的 Jupyter 笔记本中,键入以下内容以导入该库。

代码:

import pandas as pd

在这里, pd 是 Pandas 的一个标准简称或别名。

对于一些例子,我们还使用了 NumPy 库中的函数。确保安装并导入了 Pandas 和 NumPy 库。

外部文件

您需要下载一个数据集, subset-covid-data.csv ,其中包含了在特定日期各个国家与新冠肺炎疫情相关的病例和死亡人数的数据。请使用以下链接下载数据集: https://github.com/DataRepo2019/Data-files/blob/master/subset-covid-data.csv

Pandas 的积木

系列和 DataFrame 对象是 Pandas 中的底层数据结构。简而言之,一个系列就像一个列(只有一个维度),一个数据帧(有两个维度)就像一个有行和列的表格或电子表格。存储在序列或数据帧中的每个值都附有一个标签或索引,这加快了数据的检索和访问。在本节中,我们将学习如何创建系列和数据框架,以及用于操作这些对象的函数。

创建系列对象

序列是一维对象,具有一组值及其关联的索引。表 6-1 列出了创建系列的不同方式。

表 6-1

创建系列对象的各种方法

|

方法

|

句法

|
| --- | --- |
| 使用标量值 | 代码(用于使用标量值创建序列):pd.Series(2)``#Creating a simple series with just one value. Here, 0 is the index label, and 2 is the value the Series object contains.输出:0    2``dtype: int64 |
| 使用列表 | 代码(用于使用列表创建系列):pd.Series([2]*5)``#Creating a series by enclosing a single value (2) in a list and replicating it 5 times. 0,1,2,3,4 are the autogenerated index labels.输出:0    2``1    2``2    2``3    2``4    2``dtype: int64 |
| 在字符串中使用字符 | 代码(用于使用字符串创建序列):pd.Series(list('hello'))``#Creating a series by using each character in the string "hello" as a separate value in the Series. |
|   | 输出:0    h``1    e``2    l``3    l``4    o``dtype: object |
| 使用字典 | 代码(用于从字典创建序列):pd.Series({1:'India',2:'Japan',3:'Singapore'})#键/值对对应于 Series 对象中的索引标签和值。输出:1        India``2        Japan``3    Singapore``dtype: object |
| 使用范围 | 代码(用于根据范围创建系列):pd.Series(np.arange(1,5))``#Using the NumPy arrange function to create a series from a range of 4 numbers (1-4), ensure that the NumPy library is also imported输出:0    1``1    2``2    3``3    4``dtype: int32 |
| 使用随机数 | 代码(用于从随机数创建序列):pd.Series(np.random.normal(size=4))``#Creating a set of 4 random numbers using the np.random.normal function输出:0   -1.356631``1    1.308935``2   -1.247753``3   -1.408781``dtype: float64 |
| 创建带有自定义索引标签的序列 | 代码(用于创建自定义索引):pd.Series([2,0,1,6],index=['a','b','c','d'])``#The list [2,0,1,6] specifies the values in the series, and the list for the index['a','b','c','d'] specifies the index labels输出:a    2``b    0``c    1``d    6``dtype: int64 |

总之,您可以从单个(标量)值、列表、字典、一组随机数或一系列数字中创建一个 Series 对象。 pd。Series 函数创建一个 Series 对象(注意“Series”中的字母“S”是大写的;pd.series 就不行了)。如果要自定义索引,请使用 index 参数。

检查系列的属性

在这一节中,我们将研究用于找出关于 Series 对象的更多信息的方法,例如元素的数量、值和唯一元素。

找出一个数列中元素的个数

有三种方法可以找到一个系列包含的元素数量:使用大小参数、长度函数或形状参数

size 属性和 len 函数返回一个值——序列的长度,如下所示。

代码:

#series definition
x=pd.Series(np.arange(1,10))
#using the size attribute
x.size

输出:

9

我们还可以使用 len 函数来计算元素的数量,这将返回相同的输出(9),如下所示。

代码:

len(x)

shape 属性返回一个包含行数和列数的元组。因为 Series 对象是一维的,所以 shape 属性只返回行数,如下所示。

代码:

x.shape

输出:

(9,)

列出系列中各个元素的值

属性返回一个 NumPy 数组,其中包含序列中每一项的值。

代码:

x.values

输出:

array([1, 2, 3, 4, 5, 6, 7, 8, 9])

访问系列的索引

可以通过索引属性访问系列的索引。索引是具有数据类型和一组值的对象。索引对象的默认类型是 RangeIndex

代码:

x.index

输出:

RangeIndex(start=0, stop=9, step=1)

索引标签形成了一个数字范围,从 0 开始。默认步长或一个索引标签值与下一个索引标签值之差为 1。

获取序列中的唯一值及其计数

value_counts() 方法是一个重要的方法。当与 Series 对象一起使用时,它显示该对象中包含的唯一值以及每个唯一值的计数。将这种方法与分类变量一起使用是一种常见的做法,以了解它包含的不同值。

代码:

z=pd.Series(['a','b','a','c','d','b'])
z.value_counts()

输出:

a    2
b    2
c    1
d    1
dtype: int64

前面的输出显示,在名为“z”的 Series 对象中,值“a”和“b”出现了两次,而字符“c”和“d”出现了一次。

系列的方法链接

我们可以将多种方法应用于一个系列,并连续应用。这称为方法链接,可应用于 Series 和 DataFrame 对象。

示例:

假设我们想找出值“a”和“b”在下面定义的序列“z”中出现的次数。我们可以通过链接将 value_counts 方法和 head 方法结合起来。

代码:

z=pd.Series(['a','b','a','c','d','b'])
z.value_counts().head(2)

输出:

a    2
b    2
dtype: int64

如果多个方法需要一起更改并应用于一个 Series 对象,最好在单独的一行中提到每个方法,每行以反斜杠结束。这将使代码更具可读性,如下所示。

代码:

z.value_counts()\
.head(2)\
.values
Output:
array([2, 2], dtype=int64)

我们已经介绍了用于 Series 对象的基本方法。如果您想了解有关 Series 对象和 Series 对象使用的方法的更多信息,请参考以下链接。

https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.html

我们现在把 ovn 移到 DataFrames,另一个重要的 Pandas 对象。

数据帧

数据帧是系列的扩展。它是用于存储数据的二维数据结构。Series 对象包含两个组件一组值和附加到这些值上的索引标签 DataFrame 对象包含三个组件一列对象、索引对象和包含这些值的 NumPy 数组对象。

索引和列统称为轴。索引构成轴“0 ”,列构成轴“1”。

我们在表 6-2 中查看创建数据帧的各种方法。

表 6-2

创建数据帧的不同方法

|

方法

|

句法

|
| --- | --- |
| 通过组合系列对象 | 代码:student_ages=pd.Series([22,24,20]) #series 1``teacher_ages=pd.Series([40,50,45])#series 2``combined_ages=pd.DataFrame([student_ages,teacher_ages]) #DataFrame``combined_ages.columns=['class 1','class 2','class 3']#naming columns``combined_ages输出:img/498042_1_En_6_Figa_HTML.jpg这里,我们定义两个系列,然后使用 pd。DataFrame 函数创建一个名为“combined_ages”的新数据帧。我们在单独的步骤中给列命名。 |
| 从字典上 | 代码:combined_ages=pd.DataFrame({'class 1':[22,40],'class 2':[24,50],'class 3':[20,45]})``combined_ages输出:img/498042_1_En_6_Figb_HTML.jpg字典作为参数传递给 pd。DataFrame 函数(列名构成键,每列中的值包含在一个列表中)。 |
| 从 numpy 数组 | 代码:numerical_df=pd.DataFrame(np.arange(1,9).reshape(2,4))``numerical_df输出:img/498042_1_En_6_Figc_HTML.jpg这里,我们首先使用 np.arange 函数创建一个 NumPy 数组。然后我们将这个数组改造成一个两行四列的数据帧。 |
| 使用一组元组 | 代码:combined_ages=pd.DataFrame([(22,24,20),(40,50,45)],columns=['class 1','class 2','class 3'])``combined_ages输出:img/498042_1_En_6_Figd_HTML.jpg我们使用一组元组重新创建了“组合年龄”数据帧。每个元组相当于数据帧中的一行。 |

综上所述,我们可以使用一个字典,一组元组,通过组合系列对象来创建一个 DataFrame。这些方法都使用 pd。数据帧功能。注意,这个方法中的字符“D”和“F”都是大写的;pd.dataframe 不起作用。

通过从其他格式导入数据来创建数据框架

Pandas 可以使用它的阅读器功能从各种各样的格式中读取数据(参见这里支持的格式的完整列表: https://pandas.pydata.org/pandas-docs/stable/user_guide/io.html )。以下是一些常用的格式。

从 CSV 文件:

read_csv 函数可用于将 csv 文件中的数据读入数据帧,如下所示。

代码:

titanic=pd.read_csv('titanic.csv')

从 CSV 文件中读取数据是创建数据帧最常用的方法之一。CSV 文件是用于存储和检索值的逗号分隔文件,其中每行相当于一行。在调用“read_csv”功能之前,记得使用 Jupyter 主页上的上传按钮在 Jupyter 中上传 CSV 文件(图 6-1 )。

img/498042_1_En_6_Fig1_HTML.jpg

图 6-1

Jupyter 文件上传

从 Excel 文件:

Pandas 支持使用 pd.read_excel 函数从 xls 和 xlsx 文件格式导入数据,如下所示。

代码:

titanic_excel=pd.read_excel('titanic.xls')

从 JSON 文件:

JSON 代表 JavaScript Object Notation,是一种跨平台的文件格式,用于在客户机和服务器之间传输和交换数据。Pandas 提供了函数 read_json 从 json 文件中读取数据,如下所示。

代码:

titanic=pd.read_json('titanic-json.json')

从 HTML 文件:

我们还可以使用 pd.read_html 函数从 web 页面导入数据。

在下面的示例中,该函数将网页上的表解析为 DataFrame 对象。这个函数返回一个 DataFrame 对象的列表,这些对象对应于网页上的表格。在下面的例子中,table[0]对应于提到的 URL 上的第一个表。

代码:

url="https://www.w3schools.com/sql/sql_create_table.asp"
table=pd.read_html(url)
table[0]

输出:

img/498042_1_En_6_Fige_HTML.jpg

延伸阅读:查看 Pandas 中支持的格式的完整列表以及从这些格式中读取数据的函数:

https://pandas.pydata.org/pandas-docs/stable/reference/io.html

访问数据帧中的属性

在本节中,我们将了解如何访问 DataFrame 对象中的属性。

我们使用以下数据框架:

代码:

combined_ages=pd.DataFrame({'class 1':[22,40],'class 2':[24,50],'class 3':[20,45]})

属性

当与 DataFrame 对象一起使用时,index 属性给出索引对象的类型及其值。

代码:

combined_ages.index

输出:

RangeIndex(start=0, stop=2, step=1)

columns 属性提供关于列的信息(它们的名称和数据类型)。

代码:

combined_ages.columns

输出:

Index(['class 1', 'class 2', 'class 3'], dtype="object")

索引对象和列对象都是索引对象的类型。index 对象的类型为 RangeIndex ,columns 对象的类型为“Index”。index 对象的值充当行标签,而 column 对象的值充当列标签。

访问数据帧中的值

使用 values 属性,您可以获得存储在 DataFrame 中的数据。如您所见,输出是一个包含值的数组。

代码:

combined_ages.values

输出:

array([[22, 24, 20],
       [40, 50, 45]], dtype=int64)

修改数据框架对象

在这一节中,我们将学习如何更改列名以及添加和删除列和行。

重命名列

可以使用 rename 方法更改列名。字典作为参数传递给此方法。这个字典的键是旧的列名,值是新的列名。

代码:

combined_ages.rename(columns={'class 1':'batch 1','class 2':'batch 2','class 3':'batch 3'},inplace=True)
combined_ages

输出:

img/498042_1_En_6_Figf_HTML.jpg

我们使用 inplace 参数的原因是为了在实际的 DataFrame 对象中进行更改。

重命名也可以通过直接访问 columns 属性并在数组中提到新的列名来完成,如下例所示。

代码:

combined_ages.columns=['batch 1','batch 2','batch 3']

使用 dictionary 格式重命名是重命名列的一种更直接的方法,并且对原始 DataFrame 对象进行更改。这种方法的缺点是需要记住数据帧中各列的顺序。当我们使用 rename 方法时,我们使用了一个字典,其中我们知道我们正在更改哪些列名。

替换数据帧中的值或观察值

replace 方法可用于替换数据帧中的值。我们可以再次使用字典格式,用键/值对表示旧值和新值。这里,我们用值 33 替换值 22。

代码:

combined_ages.replace({22:33})

输出:

img/498042_1_En_6_Figg_HTML.jpg

向数据框架添加新列

在数据帧中插入新列有四种方法,如表 6-3 所示。

表 6-3

向数据框架添加新列

|

列插入方法

|

句法

|
| --- | --- |
| 使用索引运算符,[ ] | 代码:combined_ages['class 4']=[18,40]``combined_ages输出:img/498042_1_En_6_Figh_HTML.jpg通过在索引操作符中以字符串的形式提到列名并给它赋值,我们可以添加一个列。 |
| 使用插入的方法 | 代码:combined_ages.insert(2,'class 0',[18,35])``combined_ages输出:img/498042_1_En_6_Figi_HTML.jpginsert 方法可用于添加列。需要向该方法传递三个参数,如下所述。第一个参数是要插入新列的索引(在本例中,索引是 2,这意味着新列被添加为 DataFrame 的第三列)第二个参数是要插入的新列的名称(本例中为“class 0”)第三个参数是包含新列值的列表(在本例中是 18 和 35)对于能够成功添加列的 insert 方法来说,这三个参数都是必需的。 |
| 使用位置步进器 | 代码:combined_ages.loc[:,'class 4']=[20,40]``combined_ages输出:img/498042_1_En_6_Figj_HTML.jpgloc 索引器通常用于从序列和数据帧中检索值,但也可用于插入列。在前面的语句中,所有行都是使用:运算符选择的。该运算符后跟要插入的列的名称。该列的值包含在一个列表中。 |
| 使用串联功能 | 代码:class5=pd.Series([31,48])``combined_ages=pd.concat([combined_ages,class5],axis=1)``combined_ages输出:img/498042_1_En_6_Figk_HTML.jpg首先,要添加的列(本例中为“class5”)被定义为一个 Series 对象。然后使用 pd.concat 函数将其添加到 DataFrame 对象中。该轴需要被称为“1”,因为新数据是沿着列轴添加的。 |

总之,我们可以使用索引操作符、 loc 索引器、 insert 方法或 concat 函数向 DataFrame 添加一列。添加列的最直接、最常用的方法是使用索引运算符[]。

在数据帧中插入行

在数据帧中添加行有两种方法,要么使用 append 方法,要么使用 concat 函数,如表 6-4 所示。

表 6-4

向数据帧添加新行

|

行插入方法

|

句法

|
| --- | --- |
| 使用追加的方法 | 代码:combined_ages=combined_ages.append({'class 1':35,'class 2':33,'class 3':21},ignore_index=True)``combined_ages输出:img/498042_1_En_6_Figl_HTML.jpgappend 方法的参数——需要添加的数据——被定义为一个字典。然后这个字典作为参数传递给 append 方法。设置ignore _ index=True参数可以防止抛出错误。此参数重置索引。在使用 append 方法时,我们需要确保使用 ignore_index 参数,或者在将序列追加到 DataFrame 之前为其命名。请注意, append 方法没有确保更改反映在原始对象中的 inplace 参数;因此,我们需要将原始对象设置为指向使用 append 创建的新对象,如前面的代码所示。 |
| 使用 pd.concat 函数 | 代码:new_row=pd.DataFrame([{'class 1':32,'class 2':37,'class 3':41}])``pd.concat([combined_ages,new_row])输出:img/498042_1_En_6_Figm_HTML.jpgpd.concat 函数用于添加新行,如前面的语法所示。要添加的新行被定义为 DataFrame 对象。然后调用 pd.concat 函数,并将两个数据帧的名称(原始数据帧和定义为数据帧的新行)作为参数传递。 |

总之,我们可以使用 append 方法或 concat 函数向数据帧添加行。

从数据帧中删除列

有三种方法可以用来从数据帧中删除一列,如表 6-5 所示。

表 6-5

从数据帧中删除列

|

删除列的方法

|

句法

|
| --- | --- |
| del 功能 | 代码:del combined_ages['class 3']``combined_ages输出:img/498042_1_En_6_Fign_HTML.jpg前面的语句删除最后一列(名为“class 3”)。注意,删除就地发生,即在原始数据帧本身中。 |
| 使用弹出的方法 | 代码:combined_ages.pop('class 2')输出:0    24``1    50``Name: class 2, dtype: int64pop 方法就地删除一列,并将删除的列作为 Series 对象返回 |
| 使用下落法 | 代码:combined_ages.drop(['class 1'],axis=1,inplace=True)``combined_ages输出:img/498042_1_En_6_Figo_HTML.jpg需要删除的列在列表中以字符串的形式出现,然后作为参数传递给 drop 方法。由于默认情况下 drop 方法删除行(axis=0 ),如果我们想要删除列,我们需要将轴值指定为“1”。与 del 函数和 pop 方法不同,使用 drop 方法的删除不会发生在原始 DataFrame 对象中,因此,我们需要添加 inplace 参数。 |

综上所述,我们可以使用 del 函数、 pop 方法,或者 drop 方法从 DataFrame 中删除一列。

从数据帧中删除一行

从数据帧中删除行有两种方法——使用布尔选择或使用 drop 方法,如表 6-6 所示。

表 6-6

从数据帧中删除行

|

行删除方法

|

句法

|
| --- | --- |
| 使用布尔选择 | 代码:combined_ages[~(combined_ages.values<50)]输出:img/498042_1_En_6_Figp_HTML.jpg我们使用 NOT 操作符(~)来删除我们不想要的行。这里,我们删除数据帧中小于 50 的所有值。 |
| 使用下降的方法 | 代码:combined_ages.drop(1)输出:img/498042_1_En_6_Figq_HTML.jpg这里,我们删除了第二行,它的行索引为 1。如果要删除多行,我们需要在列表中指定这些行的索引。 |

因此,我们可以使用布尔选择或 drop 方法从数据帧中删除行。由于 drop 方法同时删除行和列,因此可以统一使用。请记住向 drop 方法添加所需的参数。要删除列,需要添加 (=1)参数。为了在原始数据帧中反映变化,需要包含就地 (=True)参数。

索引

索引是 Pandas 的基础,它使得检索和访问数据比其他工具快得多。设置适当的索引以优化性能至关重要。在 NumPy 中,索引是作为不可变(不能修改)数组实现的,并且包含可哈希的对象。可散列对象是可以根据其内容转换为整数值的对象(类似于字典中的映射)。具有不同值的对象将具有不同的哈希值。

Pandas 有两种类型的索引——行索引(垂直的),带有附加到行的标签;列索引,带有每个列的标签(列名)。

现在让我们来探索索引对象——它们的数据类型、属性,以及它们如何加速数据访问。

索引对象的类型

索引对象有一种数据类型,下面列出了其中的一些。

  • Index:这是一种通用索引类型;列索引具有这种类型。

  • range index:Pandas 中的默认索引类型(当没有单独定义索引时使用),实现为一个递增整数的范围。这种索引类型有助于节省内存。

  • Int64Index:包含整数作为标签的索引类型。对于这种索引类型,索引标签不需要等间距,而对于 RangeIndex 类型的索引,这是必需的。

  • Float64Index:包含浮点数(带小数点的数字)作为索引标签。

  • IntervalIndex:包含间隔(例如,两个整数之间的间隔)作为标签。

  • CategoricalIndex:一组有限的值。

  • DateTimeIndex:用于表示日期和时间,就像在时序数据中一样。

  • PeriodIndex:表示像季度、月或年这样的周期。

  • TimedeltaIndex:表示两个时间段或两个日期之间的持续时间。

  • MultiIndex:具有多个级别的层次索引。

延伸阅读:

在此了解更多关于指数类型的信息: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Index.html

创建自定义索引并将列用作索引

创建 Pandas 对象时,会创建一个 RangeIndex 类型的默认索引,如前所述。这种类型的索引的第一个标签值为 0(对应于 Pandas 系列或数据帧的第一项),第二个标签值为 1,后跟一个间隔为一个整数的算术级数。

我们可以使用 index 参数或属性来设置定制的索引。在我们之前创建的 Series 和 DataFrame 对象中,我们只是为单个项目设置值,在 index 对象没有标签的情况下,使用默认索引(RangeIndex 类型)。

当我们定义一个系列或数据帧时,我们可以使用 index 参数为索引标签提供自定义值。

代码:

periodic_table=pd.DataFrame({'Element':['Hydrogen','Helium','Lithium','Beryllium','Boron']},index=['H','He','Li','Be','B'])

输出:

img/498042_1_En_6_Figr_HTML.jpg

如果我们在创建对象的过程中跳过 index 参数,我们可以使用 index 属性设置标签,如下所示。

代码:

periodic_table.index=['H','He','Li','Be','B']

set_index 方法可用于使用现有列设置索引,如下所示:

代码:

periodic_table=pd.DataFrame({'Element':['Hydrogen','Helium','Lithium','Beryllium','Boron'],'Symbols':['H','He','Li','Be','B']})
periodic_table.set_index(['Symbols'])

输出:

img/498042_1_En_6_Figs_HTML.jpg

可以使用 reset_index 方法使索引再次成为列或进行重置:

代码:

periodic_table.reset_index()

输出:

img/498042_1_En_6_Figt_HTML.jpg

我们还可以在将数据从外部文件读入数据帧时设置索引,使用 index_col 参数,如下所示。

代码:

titanic=pd.read_csv('titanic.csv',index_col='PassengerId')
titanic.head()

输出:

img/498042_1_En_6_Figu_HTML.jpg

数据检索的索引和速度

我们知道索引极大地提高了访问数据的速度。让我们借助一个例子来理解这一点。

考虑以下数据帧:

代码:

periodic_table=pd.DataFrame({'Atomic Number':[1,2,3,4,5],'Element':['Hydrogen','Helium','Lithium','Beryllium','Boron'],'Symbol':['H','He','Li','Be','B']})

输出:

img/498042_1_En_6_Figv_HTML.jpg

不使用索引进行搜索

现在,尝试在不使用索引的情况下检索原子序数为 2 的元素,并使用 timeit 神奇函数测量检索所需的时间。当不使用索引时,执行线性搜索来检索元素,这相对来说比较耗时。

代码:

%timeit periodic_table[periodic_table['Atomic Number']==2]

输出:

1.66 ms ± 99.1 μs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

使用索引进行搜索

现在,将“原子序数”列设置为索引,并使用 loc 索引器查看现在搜索需要多长时间:

代码:

new_periodic_table=periodic_table.set_index(['Atomic Number'])
%timeit new_periodic_table.loc[2]

输出:

281 μs ± 14.4 μs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

在不使用索引的情况下,搜索操作的执行时间大约为毫秒级(大约 1.66 毫秒)。通过使用索引,检索操作所需的时间现在是微秒级的(281 μs ),这是一个显著的改进。

指数的不变性

如前所述,索引对象是不可变的——一旦定义,就不能修改索引对象或其标签。

例如,让我们试着改变我们刚刚定义的周期表数据帧中的一个索引标签,如下所示。我们在输出中得到一个错误,因为我们试图在一个不可变的对象上操作。

代码:

periodic_table.index[2]=0

输出:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-24-cd2fece917cb> in <module>
----> 1periodic_table.index[2]=0

~\Anaconda3\lib\site-packages\pandas\core\indexes\base.py in __setitem__(self, key, value)
   3936
   3937def __setitem__(self, key, value):
-> 3938raiseTypeError("Index does not support mutable operations")
   3939
   3940def __getitem__(self, key):

TypeError: Index does not support mutable operations

虽然不能更改索引对象的值,但是我们可以使用索引的属性来检索关于索引的信息,比如索引对象中包含的值、是否有空值等等。

让我们通过一些例子来看看一些索引属性:

考虑以下数据帧中的列索引:

代码:

periodic_table=pd.DataFrame({'Element':['Hydrogen','Helium','Lithium','Beryllium','Boron']},index=['H','He','Li','Be','B'])

column_index=periodic_table.columns

列索引的一些属性是

1.values 属性:返回列名

代码:

column_index.values

输出:

array(['Element'], dtype=object)

2.hasnans 属性:根据空值的存在返回布尔值 True 或 False。

代码:

column_index.hasnans

输出:

False

3.nbytes 属性:返回内存中占用的字节数

代码:

column_index.nbytes

输出:

8

延伸阅读:有关属性的完整列表,请参考以下文档: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Index.html

索引对齐

当添加两个 Pandas 对象时,会检查它们的索引标签是否对齐。对于具有匹配索引的项,它们的值被相加或连接。在索引不匹配的情况下,结果对象中对应于该索引的值为空( np)。南

让我们用一个例子来理解这一点。这里,我们看到 s1 中的 0 索引标签在 s2 中没有匹配,s2 中的最后一个索引标签(10)在 s1 中没有匹配。当对象被组合时,这些值等于 null。索引标签对齐的所有其他值相加在一起。

代码:

s1=pd.Series(np.arange(10),index=np.arange(10))
s2=pd.Series(np.arange(10),index=np.arange(1,11))
s1+s2

输出:

0      NaN
1      1.0
2      3.0
3      5.0
4      7.0
5      9.0
6     11.0
7     13.0
8     15.0
9     17.0
10     NaN
dtype: float64

对索引设置操作

我们可以对不同对象的索引执行集合运算,如并集、差集和对称差集。

考虑下面的索引“i1”和“i2”,它们是从我们在上一节中创建的两个 Series 对象(“s1”和“s2”)创建的:

代码:

i1=s1.index
i2=s2.index

联合操作

返回两个集合中存在的所有元素。

代码:

i1.union(i2)

输出:

Int64Index([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], dtype="int64")

差分运算

返回存在于一个集合中但不存在于另一个集合中的元素。

代码:

i1.difference(i2) #elements present in i1 but not in i2

输出:

Int64Index([0], dtype="int64")

对称差分运算

返回两个集合不共有的元素。此运算不同于差运算,因为它考虑了两个集合中的不常用元素:

代码:

i1.symmetric_difference(i2)

输出:

Int64Index([0, 10], dtype="int64")

还可以对两个索引对象执行算术运算,如下所示。

代码:

i1-i2

输出:

Int64Index([-1, -1, -1, -1, -1, -1, -1, -1, -1, -1], dtype="int64")

Pandas 中的数据类型

Pandas 中使用的数据类型是从 NumPy 派生的,除了定性数据的“category”数据类型,它是在 Pandas 中定义的。常见的数据类型包括

  • 对象(用于存储数字、字符串等混合数据。)

  • int64(用于整数值)

  • float64(用于带小数点的数字)

  • 日期时间(用于存储日期和时间数据)

  • 类别(对于只包含几个不同值的变量,如“真”/“假”,或一些有限的有序类别,如“一”/“二”/“三”/“四”)

获取有关数据类型的信息

我们现在了解了如何检索关于列的数据类型的信息。

导入 subset-covid-data.csv 文件并将数据读入 DataFrame,如下所示。

代码:

data=pd.read_csv('subset-covid-data.csv')
data.head()

输出:

img/498042_1_En_6_Figw_HTML.jpg

使用 dtypes 属性,我们可以获得这个数据帧中列的类型。

代码:

data.dtypes

输出:

country          object
continent        object
date             object
day               int64
month             int64
year              int64
cases             int64
deaths            int64
country_code     object
population      float64
dtype: object

正如我们在前一章所讨论的,对于分类变量和连续变量,可以使用的数学运算和图形的种类是不同的。了解列的数据类型有助于我们理解如何分析变量。数据类型为“object”或“category”的列是分类变量,而数据类型为“int64”和“float64”的变量是连续的。

获取每种数据类型的计数

为了获得属于每种数据类型的列数,我们使用了 get_dtype_counts 方法:

代码:

data.get_dtype_counts()

输出:

float64    1
int64      5
object     4
dtype: int64

选择特定的数据类型

使用 select_dtypes 方法,我们可以根据您想要选择的数据类型来过滤列:

代码:

data.select_dtypes(include='number').head()
#This will select all columns that have integer and floating-point data and exclude the rest. The head parameter has been used to limit the number of records being displayed.

输出:

img/498042_1_En_6_Figx_HTML.jpg

计算内存使用量和更改列的数据类型

我们可以通过使用 memory_usage 方法找到一个系列或一个数据帧的内存使用量(以字节为单位)。在使用这种方法时,我们包括了深度参数,以获得系统级内存使用的更全面的描述。

代码:

data['continent'].memory_usage(deep=True)

输出:

13030

让我们看看是否可以减少本专栏的内存使用。首先,让我们找到它当前的数据类型。

代码:

data['continent'].dtype

输出:

dtype('O')

我们可以看到,这一列占用了 13030 字节的内存,数据类型为“O”。Pandas 分类数据类型对于存储只有几个唯一值的定性变量很有用,因为这样可以减少内存使用。由于大陆列只有几个唯一的值(“欧洲”、“亚洲”、“美洲”、“非洲”、“大洋洲”),让我们将该列的数据类型从对象更改为分类,看看这是否会减少内存使用。我们使用 astype 方法来改变数据类型。

代码:

data['continent']=data['continent'].astype('category')
data['continent'].memory_usage(deep=True)

输出:

823

更改数据类型后,内存使用量似乎减少了不少。让我们计算一下确切的减少百分比。

代码:

(13030-823)/13030

输出:

0.936838066001535

通过将数据类型从 object 改为 categorical,内存使用量显著减少了约 93%。

数据子集的索引和选择

在 Pandas 中,有许多选择和访问数据的方法,如下所列。

  • lociloc 分度器

  • ix 步进器

  • atiat 索引器

  • 索引运算符[]

数据检索的首选方法是使用 lociloc 索引器。索引器和索引运算符都支持使用索引来访问对象。请注意,索引器不同于索引运算符,后者是一对包含索引的方括号。虽然我们使用了索引操作符[],用于从列表、元组和 NumPy 等对象中选择数据,但不建议使用此操作符。

例如,如果我们想要选择 Pandas 中的第一行,我们将使用下面给出的第一条语句。

代码:

data.iloc[0] #correct
data[0] #incorrect

了解 loc 和 iloc 索引器

loc 索引器通过使用索引标签选择数据来工作,这类似于 Python 中在字典中选择数据的方式,使用与值相关联的键。

另一方面, iloc 索引器 使用整数位置选择数据,这类似于列表和数组中的单个元素。

注意 lociloc 是索引器,后面是方括号,而不是圆括号(就像函数或方法的情况)。逗号前的索引值是指行索引,逗号后的索引值是指列索引。

让我们考虑一些例子来理解 lociloc 索引器是如何工作的。对于这些示例,我们再次使用新冠肺炎数据集(“subset-covid-data.csv”)。

代码:

data=pd.read_csv('subset-covid-data.csv',index_col='date')

这里,我们使用“日期”列作为索引。

选择连续的行

为此,我们可以使用 iloc ,因为我们知道要检索的行的索引(前五行):

代码:

data.iloc[0:5]

输出:

img/498042_1_En_6_Figy_HTML.jpg

请注意,我们只提到了行索引,在没有列索引的情况下,默认情况下会选择所有列。

选择连续的列

我们可以为此使用 iloc ,因为前三列的索引值(0,1,2)是已知的。

代码:

data.iloc[:,:3]

或者

data.iloc[:,0:3]

输出(仅显示前五行)

img/498042_1_En_6_Figz_HTML.jpg

虽然我们可以跳过列索引,但不能跳过行索引。

以下语法不起作用:

代码:

data.iloc[,0:3] #incorrect

在本例中,我们选择了所有的行和三列。在冒号(:)符号的两边,我们有一个开始值和一个结束值。如果起始值和终止值都缺失,则意味着要选择所有值。如果缺少起始索引,则假定默认值为 0。如果缺少停止索引值,则采用索引的最后一个可能的位置值(1 减去列数或行数)。

选择单个行

让我们使用 iloc 步进器选择第 100 行。第 100 行的索引为 99(因为索引编号从 0 开始)。

代码:

data.iloc[99]

输出:

country             Jamaica
continent           America
day                      12
month                     4
year                   2020
cases                     4
deaths                    0
country_code            JAM
population      2.93486e+06
Name: 2020-04-12, dtype: object

使用索引标签选择行

选择日期为 2020-04-12 的行。这里,我们使用 loc 索引器,因为我们知道需要选择的行的索引标签,但不知道它们的位置。

data.loc['2020-04-12']

输出(仅显示前五行):

img/498042_1_En_6_Figaa_HTML.jpg

使用名称选择列

让我们选择名为“cases”的列。由于列名充当其索引标签,因此我们可以使用 loc 索引器。

代码:

data.loc[:,'cases']

输出:

date
2020-04-12       34
2020-04-12       17
2020-04-12       64
2020-04-12       21
2020-04-12        0

使用负索引值进行选择

让我们选择前五行和后三列。这里,我们使用负索引来选择最后三列。最后一列的位置索引值为–1,倒数第二列的索引值为–2,依此类推。步长为–1。我们跳过行片的起始值(:5),因为默认值是 0。

代码:

data.iloc[:5,-1:-4:-1]

输出:

img/498042_1_En_6_Figab_HTML.jpg

选择不连续的行和列

要选择一组不连续的行或列,我们需要将行或列的索引位置或标签包含在一个列表中。在下面的示例中,我们选择第二和第五行,以及第一和第四列。

代码:

data.iloc[[1,5],[0,3]]

输出:

img/498042_1_En_6_Figac_HTML.jpg

其他(不常用的)数据访问索引器

推荐使用索引器 lociloc 从系列和 DataFrame 对象中切片或选择数据子集的原因是,它们有明确的规则来选择数据,要么只通过它们的标签(在 loc 的情况下),要么通过它们的位置(在 iloc 的情况下)。然而,理解 Pandas 中支持的其他索引器是很重要的,这将在下一节中解释。

ix 索引器

ix 索引器允许我们通过结合索引标签和位置来选择数据。这种选择方法与 lociloc 分度器使用的方法形成对比,它们不允许我们混淆位置和标签。由于 ix 索引器没有明确的规则来选择数据,因此存在很多歧义,并且该索引器现在已被弃用(这意味着尽管它仍受支持,但不应使用)。出于演示的目的,让我们看看 ix 索引器是如何工作的。让我们选择数据集中“cases”列的前五行。

代码:

data.ix[:5,'cases']

输出:

 C:\Users\RA\Anaconda3\lib\site-packages\ipykernel_launcher.py:1: DeprecationWarning:
.ix is deprecated. Please use
.loc for label based indexing or
.iloc for positional indexing

See the documentation here:
http://pandas.pydata.org/pandas-docs/stable/indexing.html#ix-indexer-is-deprecated
  """Entry point for launching an IPython kernel.

date
2020-04-12    34
2020-04-12    17
2020-04-12    64
2020-04-12    21
2020-04-12     0
Name: cases, dtype: int64

请注意,使用 ix 指示符会导致警告,要求用户使用 loc 或 iloc 来代替它。

索引运算符- [ ]

即使索引操作符不是 Pandas 中数据选择或切片的首选模式,它仍然有其用途。该运算符的一个适当应用是从数据帧中选择列。参数是以字符串形式出现的列的名称(用引号括起来)。

例如,下面的语句将从我们的 COVID 数据集中选择 population 列。

代码:

data['population']

输出(仅显示前五行):

date
2020-04-12     37172386.0
2020-04-12      2866376.0
2020-04-12     42228429.0
2020-04-12        77006.0
2020-04-12     30809762.0

为了选择多个列,我们在列表中将列名作为字符串传递,如下面的示例所示:

代码:

data[['country','population']]

输出(截断):

img/498042_1_En_6_Figad_HTML.jpg

索引运算符也可用于选择一组连续的行。

代码:

data[:3]

输出:

img/498042_1_En_6_Figae_HTML.jpg

但是,它不能用于选择一系列不连续的行,因为这将引发错误。以下语句将不起作用。

代码:

data[[3,5]] #incorrect

索引运算符的另一个限制是它不能用于同时选择行和列。下面的语句也不起作用。

代码:

data[:,3] #incorrect

at 和 iat 索引器

还有另外两个不太常用的索引器——at(类似于 loc ,与标签一起工作)和 iat (类似于 iloc ,与仓位一起工作)。 atiat 分度器的三个主要特征是

  • 它们只能用于从系列或数据帧中选择标量(单个)值。

  • 行索引和列索引都需要作为参数提供给这些索引器,因为它们返回单个值。我们不能用这个索引器获得一组行或列,而用其他索引器是可能的。

  • 这些索引器在检索数据时比 loc 和 iloc 更快。

让我们借助一个例子来理解这些索引器是如何工作的。

导入 subset-covid-data.csv 数据集。

data=pd.read_csv('subset-covid-data.csv')

处的索引器的工作方式类似于 loc,您需要将行索引标签和列名作为参数传递。

让我们尝试检索第一行中的人口值。由于我们没有为此数据帧设置索引,索引标签和位置将是相同的。

代码:

data.at[0,'population']
#0 is the index label as well as the position

输出:

37172386.0

iat 索引器类似于 iloc 索引器,行/列索引作为参数传递。

代码:

data.iat[0,9]
#0,9 is the position of the first record of the population column

输出与前一条语句的输出相同。

用于选择数据子集的布尔索引

在前面的例子中,我们使用了各种索引器来根据位置或标签检索数据。使用布尔索引,我们使用条件语句根据它们的值过滤数据。可以指定单个条件,也可以使用按位运算符- & (and)、| (or)、~ (not)组合多个条件。

让我们考虑一个例子来理解这一点。在这里,我们选择了所有洲名为“亚洲”,国名以字母“C”开头的记录。

代码:

data[(data['continent']=='Asia') & (data['country'].str.startswith('C'))]

输出:

img/498042_1_En_6_Figaf_HTML.jpg

使用查询方法检索数据

虽然我们像前面的例子一样组合了多个条件,但是代码的可读性可能会受到影响。在这种情况下可以使用查询方法。

让我们检索大陆名称为“亚洲”且病例数高于 500 的所有记录。注意语法,我们用双引号将每个条件括起来,并使用逻辑运算符,而不是位运算符&。

代码:

data.query("(continent=='Asia')""and (cases>=500)")

输出:

img/498042_1_En_6_Figag_HTML.jpg

进一步阅读

查看更多信息:

Pandas 中的操作员

Pandas 使用以下操作符,这些操作符可以应用于整个系列。虽然 Python 需要一个循环来遍历列表或字典中的每个元素,但 Pandas 利用了 NumPy 中实现的矢量化特性,该特性使这些操作符能够应用于序列中的每个元素,从而消除了对迭代和循环的需要。不同类型的操作器在表 6-7 中列出。

表 6-7

Pandas 运营商

|

操作员类型

|

操作员包括

|
| --- | --- |
| 算术运算符 | +加法)、-(减法)、(乘法)、**(乘方)、%(余数运算符)、/(除法)、//(地板除法,用于得到商)。算术运算符执行的函数可以使用以下方法复制:add for +,sub for -,mul for ,div for /,mod for %,pow for **。 |
| 比较运算符 | ==(等于),(大于),<=(less than or equal to),> =(大于等于),!=(不等于) |
| 逻辑运算符 | &,|,~.Pandas 和 NumPy 一样,使用位操作符(&、|、~)作为逻辑操作符,因为这些操作符对一个序列中的每个元素进行操作。注意,这些操作符不同于 Python 中使用的逻辑操作符,在 Python 中使用了关键字
而不是。 |

用 Pandas 来表示日期和时间

在 Pandas 中,有一个时间戳函数可以用来定义日期、时间或者日期和时间的组合。这与 Python 中的实现形成对比,Python 中的实现需要单独的对象来定义日期或时间。 pd。时间戳函数相当于 Python 中的以下函数: datetime.datedatetime.timedatetime.datetime.

举个例子,让我们用 pd 来表示 Pandas 中的日期 2000 年 12 月 25 日。时间戳功能。

代码:

pd.Timestamp('25/12/2000')

输出:

Timestamp('2000-12-25 00:00:00')

时间戳函数非常灵活,接受各种格式的参数。还可以使用以下任何语句复制前面的输出。

#different input formats for creating a Timestamp object

pd.Timestamp('25 December 2000')

pd.Timestamp('December 25 2000')

pd.Timestamp('12/25/2000')

pd.Timestamp('25-12-2000')

pd.Timestamp(year=2000,month=12,day=25)

pd.Timestamp('25-12-2000 12 PM')

pd.Timestamp('25-12-2000 0:0.0')

pd。Timestamp 函数帮助我们定义日期、时间以及这两者的组合。但是,如果我们需要定义一个持续时间,这个函数就不起作用了。一个单独的功能, pd。Timedelta ,帮助我们创建存储持续时间的对象。这相当于 Python 中的 datetime.timedelta 函数。

让我们使用 Timedelta 函数来定义 Pandas 的持续时间。

代码:

pd.Timedelta('45 days 9 minutes')

输出:

Timedelta('45 days 00:09:00')

时间戳函数一样,时间增量函数在接受什么作为输入参数方面是灵活的。前面的语句也可以写成如下形式。

代码:

pd.Timedelta(days=45,minutes=9)

我们还可以添加单位参数来创建一个时间增量对象。在下面的代码行中,值为“m”的参数单位表示分钟,我们将 00:00:00 小时的基本时间加上 500 分钟。

代码:

pd.Timedelta(500,unit='s')

输出:

Timedelta('0 days 08:20:00')

将字符串转换成 Pandas 时间戳对象

日期一般用字符串表示,需要转换成 Pandas 能理解的类型。函数的作用是:将日期转换成时间戳对象。将它转换成这种格式有助于比较两个日期,给定日期加上或减去持续时间,以及从给定日期提取单个组成部分(如日、月和年)。它还有助于表示不是传统的“日-月-年”或“月-日-年”格式的日期。

让我们考虑一个例子来理解这一点。考虑用字符串“1990 年 4 月 2 日上午 11 点 20 分和 1 日”表示的日期。我们可以将其转换为时间戳对象,并指定格式参数,以便正确解析日、月和年等各个组成部分。 pd.to_datetime 函数中的格式参数及其格式代码(如%H,%M)有助于指定日期的书写格式。%H 代表小时,%M 代表分钟,%d 代表日,%m 代表月,%Y 代表年。

代码:

a=pd.to_datetime('11:20,02/04/1990', format='%H:%M,%d/%m/%Y')
a

输出:

Timestamp('1990-04-02 11:20:00')

既然这个日期已经被转换成一个时间戳对象,我们就可以对它执行操作了。一个时间增量对象可以被添加到一个时间戳对象中。

让我们给这个日期加上四天:

代码:

a+pd.Timedelta(4,unit='d')

输出:

Timestamp('1990-04-06 11:20:00')

提取时间戳对象的组成部分

一旦使用 pd.to_datetime 函数将日期转换为 Pandas 时间戳对象,就可以使用相关属性提取日期变量的各个组成部分。

代码:

#extracting the month
a.month

输出:

4

代码:

#extracting the year
a.year

输出:

1990

代码:

#extracting the day
a.day

输出:

2

我们还可以使用分钟和小时属性从日期.中提取分钟和小时

进一步阅读

了解更多关于 Pandas 时间戳功能: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Timestamp.html

分组和聚合

聚合是将一组值汇总成单个值的过程。

统计学家哈德利·威克姆(Hadley Wickham)制定了“拆分-应用-组合”方法论(论文可在此处访问: https://www.jstatsoft.org/article/view/v040i01/v40i01.pdf ),该方法有三个步骤:

  1. 将数据分成更小的组,以便于管理和相互独立。这是在 Pandas 身上用 groupby 方法完成的。

  2. 对每个组应用函数。我们可以应用任何聚合函数,包括最小值、最大值、中值、平均值、总和、计数、标准差、方差和大小。这些聚合函数中的每一个都计算整个组的聚合值。注意,我们也可以编写一个定制的聚合函数。

  3. 将对每个组应用函数后的结果组合成一个组合对象。

在下一节中,我们将查看 groupby 方法、聚合函数、转换过滤器应用方法,以及 groupby 对象的属性。

这里,我们再次使用相同的新冠肺炎数据集,它显示了 2020 年 4 月 12 日所有国家的病例和死亡人数。

代码:

df=pd.read_csv('subset-covid-data.csv')
df.head()

输出:

img/498042_1_En_6_Figah_HTML.jpg

正如我们所看到的,有几个国家属于同一个大陆。让我们找出各大洲的病例和死亡总数。为此,我们需要使用“大陆”列进行分组。

代码:

df.groupby('continent')['cases','deaths'].sum()

输出:

img/498042_1_En_6_Figai_HTML.jpg

这里我们是按“洲”列分组,这就变成了分组列。我们正在汇总病例数和死亡数的值,这使得名为“病例”和“死亡”的列成为了汇总列。sum 方法成为了我们的聚合函数,它计算属于某个大陆的所有国家的病例和死亡总数。每当执行 groupby 操作时,建议在一开始就识别这三个元素(分组列、聚合列和聚合函数)。

以下十三个集合函数可以应用于组: sum()max()min()std()var()mean()count()size()sem()first()last()

*我们还可以使用 agg 方法,将 np.sum 作为属性,产生与前面语句相同的输出:

代码:

df.groupby('continent')['cases','deaths'].agg(np.sum)

agg 方法可以接受任何聚合方法,比如 mean、sum、max 等等,这些方法在 NumPy 中实现。

我们还可以将聚合列和聚合方法作为字典传递给 agg 方法,如下所示,这将再次产生相同的输出。

代码:

df.groupby('continent').agg({'cases':np.sum,'deaths':np.sum})

如果有多个分组列,使用列表将列名保存为字符串,并将该列表作为参数传递给 groupby 方法。

聚合函数延伸阅读: https://pandas.pydata.org/pandas-docs/stable/user_guide/groupby.html#aggregation

检查 groupby 对象的属性

应用 groupby 方法的结果是一个 groupby 对象。这个 groupby 对象有几个属性将在本节中解释。

groupby 对象的数据类型

可以使用 type 函数访问 groupby 对象的数据类型。

代码:

grouped_continents=df.groupby('continent')
type(grouped_continents)

输出:

pandas.core.groupby.generic.DataFrameGroupBy

groupby 对象的每个组都是一个单独的数据帧。

获取这些组的名称

groupby 对象有一个名为 groups 的属性。在 groupby 对象上使用这个属性将返回一个字典,这个字典的关键字是组的名称。

代码:

grouped_continents.groups.keys()

输出:

dict_keys(['Africa', 'America', 'Asia', 'Europe', 'Oceania', 'Other'])

使用第 n 种方法返回每组中位置相同的记录

假设您想要查看属于每个大洲的第四个国家的详细信息。使用第n方法,我们可以通过对第四个位置使用位置索引值 3 来检索该数据。

代码:

grouped_continents.nth(3)

输出:

img/498042_1_En_6_Figaj_HTML.jpg

使用 get_group 方法获取特定组的所有数据

使用 get_group 方法,将组名作为该方法的参数。在本例中,我们检索名为“Europe”的组的所有数据。

代码:

grouped_continents.get_group('Europe')

输出(包含 54 条记录;以下仅显示前四条记录):

img/498042_1_En_6_Figak_HTML.jpg

我们已经看到了如何将聚合函数应用于 groupby 对象。现在让我们看看其他一些函数,如 filter、apply 和 transform,它们也可以用于 groupby 对象。

过滤组

filter 方法根据特定条件删除或过滤掉组。agg (aggregate)方法为每个组返回一个值,而 filter 方法根据是否满足条件从每个组返回记录。

让我们考虑一个例子来理解这一点。我们要返回平均死亡率大于 40 的大洲的所有行。在 groupby 对象上调用了 filter 方法,并且 filter 方法的参数是 lambda 函数或预定义函数。这里的 lambda 函数计算每个组的平均死亡率,用参数“x”表示。这个参数是一个代表每个组的数据帧(在我们的例子中是大陆)。如果该组满足条件,则返回其所有行。否则,将排除该组的所有行。

代码:

grouped_continents=df.groupby('continent')
grouped_continents.filter(lambda x:x['deaths'].mean()>=40)

输出(仅显示前五行):

img/498042_1_En_6_Figal_HTML.jpg

在输出中,我们看到只返回组(大洲)“America”和“Europe”的行,因为只有这些组满足条件(组平均死亡率大于 40)。

转换方法和分组依据

变换方法是另一种可以与 groupby 对象一起使用的方法,它对组的每个值应用一个函数。它返回一个对象,该对象具有与原始数据框或数据系列相同的行,并且具有相似的索引。

让我们对 population 列使用转换方法,通过将该行中的每个值除以 1000000 来获得百万人口。

代码:

grouped_continents['population'].transform(lambda x:x/1000000)

输出(仅显示前五行和后两行;实际输出包含 206 行):

0       37.172386
1        2.866376
2       42.228429
3        0.077006
4       30.809762
.
..
...
204     17.351822
205     14.439018
Name: population, Length: 206, dtype: float64

注意, filter 方法返回的记录比它的输入对象少,而 transform 方法返回的记录数量与输入对象相同。

在前面的例子中,我们对一个系列应用了转换方法。我们也可以在整个数据帧上使用它。变换方法的一个常见应用是用来填充空值。让我们用值 0 填充数据帧中缺少的值。在输出中,请注意国家“安圭拉”的国家代码和人口的值(之前缺失)现在替换为值 0。

代码:

grouped_continents.transform(lambda x:x.fillna(0))

输出:

img/498042_1_En_6_Figam_HTML.jpg

transform 方法可用于任何系列或数据帧,而不仅仅是用于 groupby 对象。从现有列创建新列是转换方法的常见应用。

应用方法和分组依据

apply 方法将一个函数“应用”到 groupby 对象的每一组。apply 和 transform 方法的区别在于 apply 方法更灵活,因为它可以返回任何形状的对象,而 transform 方法需要返回相同形状的对象。

apply 方法可以返回单个(标量)值、序列或数据帧,并且输出不必与输入具有相同的结构。此外,当转换方法将函数应用于一个组的每一列时,应用方法将函数应用于整个组。

让我们使用应用方法来计算每个组(洲)中的总缺失值。

代码:

grouped_continents.apply(lambda x:x.isna().sum())

输出:

img/498042_1_En_6_Figan_HTML.jpg

除了 groupby 对象之外, apply 方法类似于 transform 方法,可用于 Series 和 DataFrame 对象。

如何组合 Pandas 中的对象

在 Pandas 中,有各种功能来组合两个或更多的对象,这取决于我们是想水平组合还是垂直组合它们。在本节中,我们将介绍用于组合对象的四种方法- 追加连接串联合并。

添加行的 Append 方法

此方法用于向现有 DataFrame 或 Series 对象添加行,但不能用于添加列。让我们看一个例子:

让我们创建以下数据帧:

代码:

periodic_table=pd.DataFrame({'Atomic Number':[1,2,3,4,5],'Element':['Hydrogen','Helium','Lithium','Beryllium','Boron'],'Symbol':['H','He','Li','Be','B']})

我们现在添加一个新行(以 dictionary 对象的形式),将它作为参数传递给 append 方法。

我们还需要记住将 ignore_index 参数的值设置为 True。将其设置为“True”会用新索引替换旧索引。

代码:

periodic_table.append({'Atomic Number':6,'Element':'Carbon','Symbol':'C'},ignore_index=True)

输出:

img/498042_1_En_6_Figao_HTML.jpg

注意,如果我们在使用 append 函数时跳过 ignore_index 参数,我们将得到一个错误,如下所示:

代码:

periodic_table.append({'Atomic Number':6,'Element':'Carbon','Symbol':'C'})

输出:

img/498042_1_En_6_Figap_HTML.jpg

使用 append 方法,我们还可以通过将每一行定义为一个 Series 对象并将这些 Series 对象作为一个列表传递给 append 方法来添加多行。警察局。Series 方法有一个 name 属性,它将一个索引标签分配给一个系列。

代码:

series1=pd.Series({'Atomic Number':7,'Element':'Carbon','Symbol':'C'},name=len(periodic_table))
series2=pd.Series({'Atomic Number':8,'Element':'Oxygen','Symbol':'O'},name=len(periodic_table)+1)
periodic_table.append([series1, series2])

输出:

img/498042_1_En_6_Figaq_HTML.jpg

注意,我们这次没有使用 ignore_index 参数,因为我们使用了 name 参数(参考之前显示的错误消息,其中提到我们可以在 append 方法中使用 ignore_index 参数或 name 参数)。使用 name 参数可以防止索引重置,当我们包含 ignore_index 参数时会发生这种情况。

了解各种类型的连接

在讨论组合 Pandas 对象的其他方法之前,我们需要理解内部、外部、左和右连接的概念。当联接两个对象时,联接的类型决定了这些对象中的哪些记录包含在最终结果集中。

  • 左连接:左侧对象中的所有行都包含在组合对象中。包括右侧对象中与左侧对象匹配的行。

  • 右连接:组合对象中包含的右侧对象的所有行。包括左侧对象中与左侧匹配的行。

  • 外部连接:包含在组合对象中的两个对象的所有行(无论它们是否匹配)。

  • 内部联接:只包括两个对象中匹配的行。

Concat 函数(从其他对象添加行或列)

这个函数为我们提供了向 Pandas 对象添加行和列的选项。默认情况下,它在行轴上工作并添加行。

让我们通过一个例子来看看 concat 函数是如何工作的。这里,我们垂直连接两个 DataFrame 对象。第二个 DataFrame 对象被添加到第一个 DataFrame 对象的最后一行之后。

代码:

periodic_table=pd.DataFrame({'Atomic Number':[1,2,3,4,5],'Element':['Hydrogen','Helium','Lithium','Beryllium','Boron'],'Symbol':['H','He','Li','Be','B']})
periodic_table_extended=pd.DataFrame({'Atomic Number':[8,9,10],'Element':['Oxygen','Fluorine','Neon'],'Symbol':['O','F','Ne']})
#Join these two DataFrames just created vertically using the concat function:
pd.concat([periodic_table,periodic_table_extended])

输出:

img/498042_1_En_6_Figar_HTML.jpg

我们还可以沿着列轴并排连接对象,如下所示。

代码:

pd.concat([periodic_table,periodic_table_extended],axis=1)

输出:

img/498042_1_En_6_Figas_HTML.jpg

默认情况下, concat 函数执行外部连接,返回两个对象的所有记录。串联的结果集将有五条记录(等于较长对象的长度,即第一个数据帧)。因为第二个 DataFrame 只有三行,所以您可以在最终的连接对象中看到第四行和第五行的 null 值。

我们可以通过添加参数 join 将它改为内部连接。通过使用如下所示的内部联接,带有的最终结果集只包含索引匹配的两个对象中的那些记录。

代码:

pd.concat([periodic_table,periodic_table_extended],axis=1,join='inner')

输出:

img/498042_1_En_6_Figat_HTML.jpg

我们可以使用 keys 参数来识别最终结果集中被连接的每个对象。

代码:

pd.concat([periodic_table,periodic_table_extended],axis=1,keys=['1st periodic table','2nd periodic table'])

输出:

img/498042_1_En_6_Figau_HTML.jpg

连接方法–索引到索引

join 方法基于公共索引值对齐两个 Pandas 对象。也就是说,它在两个对象中查找匹配的索引值,然后将它们垂直对齐。此方法的默认连接类型是左连接。

让我们考虑下面的例子,其中我们连接两个对象。

代码:

periodic_table.join(periodic_table_extended,lsuffix='_left',rsuffix='_right')

输出:

img/498042_1_En_6_Figav_HTML.jpg

由于在前面的例子中两个 DataFrame 对象有共同的列名,我们需要使用 lsuffixrsuffix 参数来区分它们。索引 0、1 和 2 是两个对象共有的。结果集包括第一个数据帧中的所有行,如果第二个数据帧中有索引不匹配的行,则所有这些行中的值都为空(用 NaN 表示)。用于连接方法的默认连接类型是左连接。

合并方法–基于公共列的 SQL 类型连接

连接方法一样,合并方法也用于水平连接对象。当我们用一个公共的列名连接两个 DataFrame 对象时使用它。 joinmerge 方法的主要区别在于 join 方法基于公共索引值组合对象,而 merge 方法基于公共列名组合对象。另一个区别是 merge 方法中的默认连接类型是内部连接,而 join 方法默认执行对象的左连接。

让我们通过一个例子来看看合并方法是如何工作的。这里定义的两个 DataFrame 对象有一个共同的列名——原子序数。这是一个我们可以应用合并方法的场景。

代码:

periodic_table=pd.DataFrame({'Atomic Number':[1,2,3,4,5],'Element':['Hydrogen','Helium','Lithium','Beryllium','Boron'],'Symbol':['H','He','Li','Be','B']})
periodic_table_extended=pd.DataFrame({'Atomic Number':[1,2,3],'Natural':'Yes'})
periodic_table.merge(periodic_table_extended)

输出:

img/498042_1_En_6_Figaw_HTML.jpg

公共列名的存在对于合并操作是必不可少的,否则我们会得到一个错误。如果两个数据帧之间有一个以上的公共列,我们可以使用 on 参数指定要执行合并的列。

我们可以使用 how 参数改变默认的连接类型(内部连接)。

代码:

periodic_table.merge(periodic_table_extended,how='outer')

输出:

img/498042_1_En_6_Figax_HTML.jpg

如果我们在被连接的两个对象中有相同的列,但是它们的名称不同,我们可以在 merge 方法中使用参数来区分这些列。

在下面的示例中,有两个值相同但名称不同的列。在第一个 DataFrame 对象中,列的名称是“原子序数”,而在第二个 DataFrame 对象中,列的名称是“数字”。

代码:

periodic_table=pd.DataFrame({'Atomic Number':[1,2,3,4,5],'Element':['Hydrogen','Helium','Lithium','Beryllium','Boron'],'Symbol':['H','He','Li','Be','B']})
periodic_table_extended=pd.DataFrame({'Number':[1,2,3],'Natural':'Yes'})

使用左边列的 left_on 参数和右边列的 right_on 参数,我们合并两个对象如下:

代码:

periodic_table.merge(periodic_table_extended,left_on='Atomic Number',right_on='Number')

输出:

img/498042_1_En_6_Figay_HTML.jpg

注意, appendmergejoin 都是 DataFrame 对象使用的 DataFrame 方法,而 concat 是一个 Pandas 函数。

延伸阅读:

合并方法: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.merge.html

加入方式: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.join.html#pandas.DataFrame.join

串联功能: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.concat.html

追加方式: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.append.html

重构数据和处理异常

正如我们前面所讨论的,原始状态的数据通常是杂乱的,不适合分析。大多数数据集在适合分析和可视化之前需要大量的处理。下面列出了数据集中最常见的问题。

  • 数据缺少值。

  • 列名是不可理解的。

  • 变量存储在行和列中。

  • 一列可以代表多个变量。

  • 在一个表中有不同的观察单位。

  • 存在数据重复。

  • 数据是围绕错误的轴构建的(例如,水平轴而不是垂直轴)。

  • 变量是分类的,但是我们需要它们是数字格式的,以便进行计算和可视化。

  • 无法正确识别变量的数据类型。

  • 数据包含需要删除的空格、逗号、特殊符号等等。

在下面几节中,我们将了解如何处理缺失和重复的数据,如何将数据从宽格式转换为长格式,以及如何使用各种方法,如 pivotstackmelt

处理缺失数据

Pandas 中缺失的数据由值 NaN ( 不是数字)表示,表示为关键字 np.nan 。我们可以使用 isnaisnull 方法来查找空值。对于 Series 或 DataFrame 中的每个对象,这两种方法都返回 True(对于缺失值)或 False(对于所有其他值)布尔值。

让我们看看降雨量数据集中有多少空值。

代码:

df=pd.read_csv('subset-covid-data.csv')
df.isna().sum().sum()

输出:

8

该数据集中有八个空值。 sum 方法使用两次。第一个 sum 方法计算每列缺失值的总数,第二个 sum 方法将这些值相加,得出整个数据帧中缺失值的数量。

我们有两种选择来处理这些缺失的数据——要么我们去除这些值(丢弃它们),要么我们用合适的度量(如平均值、中值或众数)来替代这些值,这些度量可以用作缺失值的近似值。让我们来看看每一种方法。

丢弃丢失的数据

dropna 方法删除数据帧或系列中所有缺失的值。

代码:

df.dropna()

请注意,此方法会创建数据帧的副本,而不会修改原始数据帧。为了修改原始数据帧,我们使用 inplace=True 参数。

代码:

df.dropna(inplace=True)

归罪

插补是替换缺失值的过程。在 Pandas 中,可以使用 fillna 方法,用均值、中值或众数等集中趋势的度量来替代缺失值。您还可以用固定值或常数值(如 0)来填充缺少的值。

我们可以使用向前填充技术用它之前的值填充丢失的值,或者使用向后填充技术用它之后的值替换空值。

使用相同的数据集(subset-covid-data.csv),让我们尝试理解向前填充和向后填充的概念。

代码:

data=pd.read_csv('subset-covid-data.csv')
df=data[4:7]
df

正如我们所看到的,DataFrame 对象 df(从原始数据集创建)缺少值。

img/498042_1_En_6_Figaz_HTML.jpg

让我们使用向前填充技术( ffill ),用国家`安圭拉'的 NaN 值替换它前面的值(在前一行中),如下所示。

代码:

df.fillna(method='ffill')

输出:

img/498042_1_En_6_Figba_HTML.jpg

正如我们所看到的,安圭拉的人口字段被安哥拉的相应值所替代(在它之前的记录)。

我们还可以使用反向填充技术“bfill”来替换丢失的值,该技术用下一行中出现的下一个有效观察值来替换丢失的值。

代码:

df.fillna(method='bfill')

输出:

img/498042_1_En_6_Figbb_HTML.jpg

安圭拉缺失的人口数值现在由下一行的相应数值(安提瓜和巴布达)取代。

缺失值插补的标准方法是用其他有效观察值的平均值代替空值。fillna 方法也可以用于此目的。

这里,我们将每列中缺失的值替换为该列中其他两个值的平均值。

代码:

df.fillna(df.mean())

输出:

img/498042_1_En_6_Figbc_HTML.jpg

在 DataFrame 对象 df 中,安圭拉缺失的人口数值现在由其他两个国家(安哥拉和安提瓜和巴布达)的人口数字的平均值代替。

插值是估计数字列中缺失值的另一种技术,最直接的插值技术是线性插值方法。在线性插值中,直线方程用于根据已知值估计未知值。如果有两个点,( x 0y 0 )和( x 1、 y 1 ),那么一个未知点(x,y)可以用下面的等式来估计:

$$ y-{y}_0=\left(\frac{y_1-{y}_0}{x_1-{x}_0}\right)\left(x-{x}_0\right) $$

在 Pandas 中,有一种插值方法,可以从已知值中估计未知值。

代码:

df.interpolate(method='linear')

输出:

img/498042_1_En_6_Figbd_HTML.jpg

每列中缺失的值使用该列中的其他值进行插值。

延伸阅读:查看更多关于 Pandas 缺失数据: https://pandas.pydata.org/pandas-docs/stable/user_guide/missing_data.html

数据复制

由于许多记录包含相同的数据,数据中的冗余是一种常见现象。

让我们考虑以下数据框架:

代码:

periodic_table=pd.DataFrame({'Atomic Number':[1,2,3,4,5,5],'Element':['Hydrogen','Helium','Lithium','Beryllium','Boron','Boron'],'Symbol':['H','He','Li','Be','B','B']})

img/498042_1_En_6_Figbe_HTML.jpg

正如我们所看到的,这个数据中有重复项(最后两行是相同的)。

在 Pandas 中,可以使用重复方法来检测重复的存在。复制的方法为每一行返回一个布尔值,如下所示。因为第五行是第四行的副本,所以布尔值为真。

代码:

periodic_table.duplicated()

输出:

0    False
1    False
2    False
3    False
4    False
5     True
dtype: bool

通过将此方法与 sum 方法链接起来,我们可以找到数据帧中重复项的总数。

代码:

periodic_table.duplicated().sum()

输出:

1

现在让我们使用 drop_duplicates 方法来删除重复项。

代码:

periodic_table.drop_duplicates(inplace=True)

输出:

img/498042_1_En_6_Figbf_HTML.jpg

重复的行已被删除。由于 drop_duplicates 方法不对实际的 DataFrame 对象进行更改,我们需要使用 inplace 参数。

默认情况下, drop_duplicates 方法保留所有重复行中的第一行。如果我们想保留最后一行,我们可以使用参数 keep='last' ,如下所示。

代码:

periodic_table.drop_duplicates(keep='last')

输出:

img/498042_1_En_6_Figbg_HTML.jpg

除了处理冗余或缺失的数据,我们可能需要替换那些对我们的分析没有价值的数据,比如空格或特殊字符。可以使用前面讨论过的 replace 方法来删除或替换值。我们可能还需要更改列的数据类型,因为 Pandas 可能无法正确识别所有的数据类型,这可以使用“astype”方法来完成。

整理数据和重组数据的技术

整齐的数据是由哈德利·韦翰发明的一个术语。根据他撰写的一篇论文(链接: http://vita.had.co.nz/papers/tidy-data.pdf ),整理数据的三大原则是:

  1. 列对应于数据中的变量,每个变量映射到一个列。

  2. 这些行只包含观察值,不包含变量。

  3. 每个数据结构或表格只包含一个观察单位。

请注意,使数据整洁不同于数据清理。数据清理涉及处理缺失值和冗余信息、删除填充字符以及更改不准确的数据类型。另一方面,将数据转换成整齐的格式需要重新组织数据并沿右轴排列,以便于分析。

让我们用一个例子来理解这一点,使用下面的数据框架。

img/498042_1_En_6_Figbh_HTML.jpg

前面的数据帧显示了四名学生的年龄(以岁为单位)和身高(以厘米为单位)。虽然这些数据是可读的,但不是“整齐”的形式。此数据框架有三个问题违反了数据整洁的原则:

  • 学生的姓名不能用作列名。相反,我们需要用一个变量来代表所有的名字。

  • 属性“年龄”和“身高”不应用作行中的观察值。它们实际上是独立的变量,应该是单独的列。

  • 在同一数据框架中有两种不同的观察单位——测量年龄的年和测量身高的厘米

长格式的数据被认为是整齐的,在下一节中,我们将介绍 Pandas 中把数据集转换成这种结构的方法。

从宽到长格式的转换(整齐的数据)

以下是两个数据帧,数据相同,但结构不同(宽和长)。

宽格式

img/498042_1_En_6_Figbi_HTML.jpg

长格式

img/498042_1_En_6_Figbj_HTML.jpg

转换为长格式的主要好处是,这种格式便于数据操作,如添加或检索数据,因为我们不需要担心数据的结构。此外,当数据以长格式存储时,数据检索明显更快。

让我们用这个例子来理解。

首先,创建以下数据帧:

代码:

grades=pd.DataFrame({'Biology':[90,87,45],'Chemistry':[46,56,87],'Mathematics':[95,74,45],'Physics':[75,65,33]},index=['Andrew','Sarah','Jason'])
grades

输出:

img/498042_1_En_6_Figbk_HTML.jpg

在这个数据框架中,我们可以看到整洁数据的原则没有得到遵守。有两个主要变量(学生和科目)没有被标识为列。可变科目(如生物、化学、数学和物理)的值是观察值,不应用作列。

堆栈方法(宽到长格式转换)

我们可以使用 stack 方法纠正 grades 数据帧中观察到的异常,该方法获取所有列名并将它们移动到索引中。此方法返回一个新的数据帧或系列,带有多级索引。

代码:

grades_stacked=grades.stack()
grades_stacked

输出:

Andrew  Biology        90
        Chemistry      46
        Mathematics    95
        Physics        75
Sarah   Biology        87
        Chemistry      56
        Mathematics    74
        Physics        65
Jason   Biology        45
        Chemistry      87
        Mathematics    45
        Physics        33
dtype: int64

如前面的输出所示,该结构已经从宽格式更改为长格式。

让我们检查一下这个堆叠对象的数据类型。

代码:

type(grades_stacked)

输出:

pandas.core.series.Series

我们可以看到,这是一个系列对象。我们可以使用 reset_index 方法将该对象转换为 DataFrame,这样两个变量——Name 和 Subject——可以被标识为两个独立的列:

代码:

grades_stacked.reset_index()

输出:

img/498042_1_En_6_Figbl_HTML.jpg

在前面的输出中,我们使用 rename_axis 方法更改了列的名称,并重置了索引,如下所示。

代码:

grades_stacked.rename_axis(['student_name','subject']).reset_index(name='marks')

输出:

img/498042_1_En_6_Figbm_HTML.jpg

要将此数据帧转换回其原始(宽)格式,我们使用 unstack 方法,如下所示:

代码:

grades_stacked.unstack()

输出:

img/498042_1_En_6_Figbn_HTML.jpg

熔化方法(宽到长格式转换)

除了方法之外,融化方法也可以用于将数据转换为长格式。熔化方法比堆叠方法更加灵活,它提供了一个选项来添加定制输出的参数。

让我们创建相同的数据帧(宽格式):

代码:

grades=pd.DataFrame({'Student_Name':['Andrew','Sarah','Jason'],'Biology':[90,87,45],'Chemistry':[46,56,87],'Mathematics':[95,74,45],'Physics':[75,65,33]})

img/498042_1_En_6_Figbo_HTML.jpg

现在,使用熔化方法将其转换为长格式。

代码:

grades.melt(id_vars='Student_Name',value_vars=['Biology','Chemistry','Physics','Mathematics'],var_name='Subject',value_name='Marks')

输出:

img/498042_1_En_6_Figbp_HTML.jpg

我们在熔融法中使用了四个变量:

  • id_vars:我们不想改变形状并保留其当前形式的列。如果我们查看 grades 数据帧的原始宽格式,则 Student_Name 的结构是正确的,并且可以保持原样。

  • value_vars: Variable 或我们要将其整形为单列的变量。在等级数据框架的宽版本中,四个科目中的每一个都有一列。这些实际上是单个列的值。

  • var_name:重新整形后的新列的名称。我们希望创建一个单独的列——“主题”,值为“生物”、“化学”、“物理”和“数学”。

  • value_name:这是列(“标记”)的名称,其中包含的值对应于经过整形的列(“主题”)的值。

透视方法(长到宽转换)

pivot 方法是另一种重塑数据的方法,但与 meltstack 方法不同,这种方法将数据转换成宽格式。在下面的例子中,我们用 pivot 方法反转熔化操作的效果。

代码:

#original DataFrame
grades=pd.DataFrame({'Student_Name':['Andrew','Sarah','Jason'],'Biology':[90,87,45],'Chemistry':[46,56,87],'Mathematics':[95,74,45],'Physics':[75,65,33]})
#Converting to long format with the wide method
grades_melted=grades.melt(id_vars='Student_Name',value_vars=['Biology','Chemistry','Physics','Mathematics'],var_name='Subject',value_name='Marks')
#Converting back to wide format with the pivot method
grades_melted.pivot(index='Student_Name',columns='Subject',values='Marks')

输出:

img/498042_1_En_6_Figbq_HTML.jpg

以下参数用于 pivot 方法:

  • 索引:指作为索引使用的列。在年级数据框架的宽格式中,我们使用学生姓名作为索引。

  • :列的名称,其值用于创建一组新的列。“主题”列的每个值在宽格式中形成一个单独的列。

  • :用于填充 DataFrame 中的值的列。在我们的分数例子中,“分数”栏就是用于这个目的的。

延伸阅读:

融化功能: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.melt.html

枢纽功能: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.pivot.html

摘要

  1. Pandas 是一个用于处理数据的 Python 库,它提供了许多优势,包括支持导入数据的各种输入格式、与其他库的集成、速度以及清理、分组、合并和可视化数据的功能。

  2. 系列、数据框架和索引构成了 Pandas 的核心。系列是一维的,相当于列,而数据帧是二维的,相当于表格或电子表格。序列和数据帧使用通过哈希表实现的索引来加速数据检索。

  3. 有多种方法可以创建和修改 Series 和 DataFrame 对象。Python 对象,如列表、元组和字典,以及 NumPy 数组,可以用来创建 Pandas 对象。我们可以添加和删除行或列,替换值,以及重命名列。

  4. Pandas 中的数据检索可以通过使用位置( iloc 索引器)或索引标签( loc 索引器),或者通过指定条件(布尔条件)来完成。

  5. Pandas 使用 groupby 函数进行聚合。各种函数可以与 groupby 函数结合使用,以计算每个组的聚合度量,根据条件过滤组,转换值,或对它们应用运算。

  6. Pandas 支持组合、连接和合并两个或多个对象。 append 方法将一个对象垂直添加到现有对象中, concat 函数可以并排或垂直添加对象。 joinmerge 方法基于公共列或索引值水平连接对象。

  7. 整齐数据是指这样一种结构,其中列对应于数据集中的变量,行包含观察值,并且只有一个观察单位。通常,长(垂直)格式的数据被认为是整齐的。像 melt 和 stack 这样的函数将数据帧转换成长格式。拆分和透视功能将数据转换为宽格式。

既然我们已经学习了如何准备数据并使其为分析做好准备,让我们在下一章看看数据可视化。

复习练习

问题 1

用 Pandas 为编写函数/索引器/属性

  • 从 HTML 文件导入数据

  • 将数据导出到 Excel 文件

  • 使用索引位置选择数据

  • 使用标签选择数据

  • 用中值替换空值

  • 重命名列

  • 获取数据帧中的行数和列数

  • 使用 pivot 函数转换为宽格式

  • 执行两个表的内部连接

  • 更改系列的数据类型

问题 2

df.describe 函数返回以下汇总统计值:计数、最小值、最大值、标准偏差、百分位数和平均值。

您将添加哪个参数来获得以下结果:唯一值的数量、出现最频繁的值以及该值的频率?

问题 3

在数据帧中导入“subset-covid-data.csv”。选择以下数据:

  1. 国家和大陆列

  2. 将“国家”列设置为索引,并使用 at 或 loc 索引器检索国家“阿尔及利亚”的人口。

  3. 使用 iloc 或 iat 索引器选择第 50 和第 3 的值。

  4. 检索最后三条记录的国家代码和人口数据。

  5. 选择人口超过 250 万且病例数超过 3000 的国家的数据。

问题 4

在 DataFrame 中导入文件“subset-covid-data.csv”的数据,并编写以下代码语句:

  1. 删除“国家代码”列

  2. 将此列插回到它之前所在的位置

  3. 删除前三行

  4. 将这些行添加回来(在它们的原始位置)

问题 5

创建以下数据框架:

DataFrame name: orders_df

img/498042_1_En_6_Figbr_HTML.jpg

DataFrame name: orders1_df

img/498042_1_En_6_Figbs_HTML.jpg

DataFrame name:customers_df

img/498042_1_En_6_Figbt_HTML.jpg

您会使用哪种功能或方法来:

  1. 结合前两个 DataFramesorders _ df 和 orders1_df 的细节?

  2. 创建一个数据框架来显示客户和他们订购的商品?

  3. 是否将 order_id 列作为 orders_df 和 customers_df 的索引?您现在将使用哪种方法来组合这两个对象,以显示客户下了哪些订单?

问题 6

下面的数据框记录了四个人的体重波动:

img/498042_1_En_6_Figbu_HTML.jpg

  1. 创建前面的数据帧。

  2. 把这个数据帧转换成整齐的格式。

  3. 确定这四个人中谁的体重波动最小。

  4. 对于平均体重低于 65 公斤的人,将他们的体重(所有四天)转换为磅并显示该数据。

问题 7

对象数据类型用于包含以下数据的列:

  1. 用线串

  2. 数字(int 和 float)

  3. 布尔运算

  4. 所有前述数据类型的组合

问题 8

可以使用点符号(a.column_name)和索引运算符(a[column_name])来访问列。哪一种是首选的符号,为什么?

问题 9

哪种方法用于获取数据帧中的元数据?

  1. 描述方法

  2. 数值计数法

  3. 信息方法

  4. 以上都不是

问题 10(填空)

  • 连接方法的默认连接类型是 ____,添加连接类型的参数是 ___

  • 合并方法的默认连接类型是 ____,添加连接类型的参数是 ___

  • concat 函数的默认连接类型是 ____,用于添加连接类型的参数是 ___

  • Pandas 中的函数创建了一个表示日期/时间值的对象,并等效于以下 Python 函数:datetime.date、datetime.time 或 datetime.datetime 是 ___

  • Pandas 中表示持续时间的函数相当于 Python 中的 datetime.timedelta 函数,是 ___

答案

问题 1

  • 从 HTML 文件导入数据:

代码:

  • 将数据导出到 Excel 文件:
pd.read_html(url)

代码:

  • 使用索引位置选择数据:
df.to_excel(name_of_file)

代码:

  • 使用标签选择数据:
df.iloc[index position]

代码:

  • 用中值替换数据帧中的空值:
df.loc[column name or index label]

代码:

  • 重命名列:
df.fillna(df.median())

  • 获取数据帧中的行数和列数:
df.rename(columns={'previous_name:'new_name'})

代码:

  • 使用枢轴功能转换为宽格式:
df.shape

代码:

  • 执行两个 DataFrame 对象的内部连接
df.pivot(index=column_name1,
columns=column_name2, values=column_name3)

代码:

df1.merge(df2)

对于合并方法,默认的连接类型是内部;因此,我们不需要明确提到它。

  • 更改系列对象/列的数据类型:

代码:

series_name.astype(new data type)

问题 2

我们可以使用 include='all '参数获得所有三个值,如下所示:

代码:

df.describe(include='all')

问题 3

代码:

df=pd.read_csv('subset-covid-data.csv')

1.检索国家和大陆列

代码:

df[['country','continent']]

2.将“国家”列设置为索引,并使用 at 或 loc 索引器检索国家“阿尔及利亚”的人口。

代码:

df.set_index('country',inplace=True)

使用以下语句检索总体:

代码:

df.at['Algeria','population']

或者

df.loc['Algeria','population']

3.使用 iloc 或 iat 索引器选择第 50 和第 3 的值。

df.iat[49,2]

或者

df.iloc[49,2]

4.检索最后三条记录的国家代码和人口数据:

代码:

df.iloc[203:,-1:-3:-1]

或者

代码:

df.iloc[203:,7:]

5.选择人口超过 250 万且病例数超过 3000 的国家的数据。

代码:

df[(df['cases']>=3000) & (df['population']>=25000000)]

问题 4(行和列的添加和删除)

  1. 删除“国家代码”栏:

    代码:

  2. 将此列插回之前的位置:

    代码:

x=df.pop('country_code')

  1. 删除前三行:

    代码:

df.insert(8,'country_code',x)

  1. 将这些行添加回数据帧的原始位置:

    CODE:

    x=df.iloc[0:3]
    pd.concat([x,df])
    
    
df.drop([0,1,2],inplace=True)

问题 5

创建以下数据框架:

代码:

orders_df=pd.DataFrame({'order_id':[1,2,3],'item':['pens','shirts','coffee']})
orders1_df=pd.DataFrame({'order_id':[4,5,6],'item':['crayons','tea','fruits']})
customers_df=pd.DataFrame({'order id':[1,2,3],'customer_name':['anne','ben','carlos']})

用于组合对象的函数

  • 结合两个数据帧 orders_df 和 orders1_df 的详细信息:

    代码:

  • 创建一个组合数据框架,显示客户及其订购的商品:

    代码:

         pd.concat((orders_df,orders1_df))

  • 将 order_id/order id 列作为“orders_df”和“customers_df”数据帧的索引:

    CODE:

    orders_df.set_index('order_id',inplace=True)
    customers_df.set_index('order id',inplace=True)
    
    

    方法来组合这两个对象,以显示客户下了哪些订单:

    代码:

pd.merge(orders_df,customers_df,left_on='order_id',right_on='order id')

orders_df.join(customers_df)

问题 6(整理数据和汇总)

  1. 创建此数据框架。

    CODE:

    df=pd.DataFrame({'Anna':[51,52,51.4,52.8,50.5],'Ben':[70,70.5,69.1,69.8,70.5],'Carole':[64,64.2,66.8,66,63.4],'Dave':[81,81.3,80.5,80.9,81.4]})
    
    
  2. 把这个转换成整齐的格式。

    我们使用 melt 方法将数据帧转换为长格式,然后重命名列。

    CODE:

    df_melted=df.melt()
    df_melted.columns=['name','weight']
    
    
  3. 为了找出这四个人中谁的体重相对于他们的平均体重波动最小,我们需要首先汇总数据。

    我们使用 groupby 函数根据人名对数据进行分组,并使用标准差( np.std )聚合函数。

    代码:

df_melted.groupby('name').agg({'weight':np.std})

输出:

img/498042_1_En_6_Figbv_HTML.jpg

从前面的输出中可以看出,戴夫的标准差最小,因此他的体重波动最小。

  1. 对于平均体重低于 65 公斤的人,我们现在需要将他们的体重转换为磅,并显示他们四天的体重。

    我们使用过滤方法过滤出平均重量大于 65 千克的组,然后应用转换方法将重量转换为磅。

    CODE:

    df_melted.groupby('name').filter(lambda x:x.mean()>65)['weight'].transform(lambda x:float(x)*2.204)
    
    

问题 7

选项 1 和 4

Pandas 中的 object 数据类型用于包含数字、字符串等混合数据的字符串或列。

问题 8

首选索引运算符[],因为它不会与内置方法冲突,并且可以处理包含空格和特殊字符的列名。当您必须检索包含空格的列时,点符号不起作用。

问题 9

选项 3–信息方法。

info 方法用于获取 Series 或 DataFrame 对象的元数据。它为我们提供了列的名称、它们的数据类型、非空值的数量以及内存使用情况。

第 10 题(填空题)

连接方法的默认连接类型是"左连接,添加连接类型的参数是" how 参数

合并方法的默认连接类型是一个“内部连接”,添加连接类型的参数是“如何”参数

concat 函数的默认连接类型是一个“外部连接”,用于添加连接类型的参数是“连接”参数

Pandas 中的函数创建了一个表示日期/时间值的对象,相当于以下 Python 函数: datetime.datedatetime.timedatetime.datetimepd。时间戳

Pandas 中与 Python 中的 datetime.timedelta 函数等效的用于表示持续时间的函数是 pd。时间增量*

七、使用 Python 库实现数据可视化

在最后一章中,我们阅读了关于 Pandas 的内容,这是一个具有各种功能的库,用于准备数据,以便为分析和可视化做好准备。可视化是一种理解数据模式、识别异常值和其他兴趣点,并将我们的发现呈现给外部受众的方法,而无需手动筛选数据。可视化还有助于我们从原始数据中收集信息,并获得难以获得的洞察力。

阅读完本章后,您将能够理解常用的绘图,理解 Matplotlib 中面向对象和有状态的方法并应用这些方法进行可视化,学习如何使用 Pandas 进行绘图,并理解如何使用 Seaborn 创建图形。

技术要求

在 Jupyter 笔记本中,键入以下内容以导入以下库。

代码:

import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

这里, plt 是我们用来绘图的 Matplotlib 的 pyplot 模块的简称或者别名, sns 是 Seaborn 库的别名, pd 是 Pandas 的别名。

如果没有安装这些库,请转到 Anaconda 提示符并按如下方式安装它们:

pip install matplotlib
pip install seaborn
pip install pandas

外部文件

我们在本章中使用 Titanic 数据集来演示各种图。

请使用以下链接下载数据集: https://github.com/DataRepo2019/Data-files/blob/master/titanic.csv

您也可以使用以下步骤下载该数据集:

img/498042_1_En_7_Figa_HTML.jpg

  • 从下载的 zip 文件夹中,打开“titanic.csv”文件

常用地块

在探索性或描述性数据分析中广泛使用的一些基本图包括条形图、饼图、直方图、散点图、箱线图和热图;这些在表 7-1 中解释。

表 7-1

描述性数据分析中可视化数据的常用图

|

图表或绘图的类型

|

描述

|

形状

|
| --- | --- | --- |
| 条形图 | 条形图使分类数据可视化,条形的宽度或高度代表每个类别的值。条形图可以垂直或水平显示。 | img/498042_1_En_7_Figb_HTML.jpg |
| 柱状图 | 直方图用于可视化连续变量的分布。它将连续变量的范围划分为多个区间,并显示大部分值所在的位置。 | img/498042_1_En_7_Figc_HTML.jpg |
| 箱线图 | 箱形图有助于直观地描述数据的统计特征。箱形图提供了五点汇总,图中的每条线代表所绘制数据的统计测量值(参见右图)。这五项措施是最小值第 25 个百分位数中位数(第 50 个百分位数)第 75 个百分位数最大值您在右图中看到的小圆圈/点代表异常值(或极端值)。方框两边的两条线分别代表最小值和最大值,也称为“触须”。这些须状物之外的任何点都被称为异常值。框中的中线代表中位数。箱线图通常用于连续(比率/区间)变量,尽管它也可以用于一些分类变量,如顺序变量。 | img/498042_1_En_7_Figd_HTML.jpg |
| 饼图 | 饼图将变量的不同值显示为圆内的扇形。饼图与分类变量一起使用。 | img/498042_1_En_7_Fige_HTML.jpg |
| 散点图 | 散点图将两个连续变量的值显示为 x 轴和 y 轴上的点,帮助我们直观地了解这两个变量是否相关。 | img/498042_1_En_7_Figf_HTML.jpg |
| 热图 | 热图使用颜色编码矩阵显示多个变量之间的相关性,其中颜色饱和度表示变量之间的相关性强度。热图有助于同时显示多个变量。 | img/498042_1_En_7_Figg_HTML.jpg |

现在让我们看看一些用于可视化的 Python 库,从 Matplotlib 开始。

Matplotlib

Python 中数据可视化的主要库是 Matplotlib。Matplotlib 有许多类似于 Matlab(一种带有绘图工具的计算环境和编程语言)的可视化特性。Matplotlib 主要用于绘制二维图形,对创建三维图形的支持有限。

与其他库(如 Seaborn 和 Pandas)相比,使用 Matplotlib 创建的绘图需要更多的代码行和绘图参数的定制(使用一些默认设置来简化创建绘图的代码编写)。

Matplotlib 构成了使用 Python 执行的大多数可视化的主干。

Matplotlib 中有两个接口,有状态的和面向对象的,在表 7-2 中有描述。

表 7-2

Matplotlib 中的接口

|

有状态接口

|

面向对象的接口

|
| --- | --- |
| 这个接口使用了基于 Matlab 的 pyplot 类。创建此类的单个对象,用于所有绘图目的。 | 在这个界面中,我们为不同的绘图元素使用不同的对象。该界面中用于绘图的两个主要对象是人物对象,作为其他对象的容器。对象,它是包含 x 轴、y 轴、点、线、图例和标签的实际绘图区域。注意,这里的轴不是指 x 和 y 轴,而是指整个子情节。 |
| 代码示例(使用有状态接口的可视化) :import matplotlib.pyplot as plt``%matplotlib inline``x=[1,2,3,4]``y=[3,6,9,12]``plt.plot(x,y) # The plot function plots a line between the x and y coordinates``plt.xlim(0,5) # Sets limits for the x axis``plt.ylim(0,15) # Sets limits for the y axis | 代码示例(使用面向对象界面的可视化):import matplotlib.pyplot as plt``%matplotlib inline``x=[1,2,3,4]``y=[3,6,9,12]``fig,ax=plt.subplots(figsize=(10,5)) #The subplots method creates a tuple returning a figure and one or more axes.``ax.plot(x,y) #Creating a plot with an axes object |
| plt.xlabel('X axis') #labels the x axis``plt.ylabel('Y axis') #labels the y axis``plt.title('Basic plot') #Gives a title``plt.suptitle('Figure title',size=20,y=1.02) # Gives a title to the overall figure定制(使用有状态接口):在此界面中,所有更改都是使用指向图形或轴的 pyplot 对象的当前状态进行的,如下所示。代码示例:#This code makes changes to the graph created using the plt object``ax=plt.gca() # current axes``ax.set_ylim(0,12) #use it to set the y axis limits``fig=plt.gcf() #current figure``fig.set_size_inches(4,4) #use it to set the figure size | 定制(使用面向对象的界面):在这个界面中,因为我们有不同的对象用于图形和每个子图或轴,这些对象被单独定制和标记,如下所示。代码示例:#this code makes changes to the graph created using the preceding object-oriented interface``ax.set_xlim(0,5) # Sets limit for the x axis``ax.set_ylim(0,15) # Sets limit for the y axis``ax.set_xlabel('X axis') #Labels x axis``ax.set_ylabel('Y axis') #Labels y axis``ax.set_title('Basic plot') # Gives a title to the graph plotted``fig.suptitle('Figure title',size=20,y=1.03) #Gives a title to the overall figure |

延伸阅读:查看更多关于这两种不同界面: https://matplotlib.org/3.1.1/tutorials/introductory/lifecycle.html

使用 Matplotlib 绘图的方法

由于能够控制和自定义每个单独的对象或绘图,因此推荐使用面向对象的方法在 Matplotlib 中绘图。以下步骤使用面向对象的方法进行绘图。

  1. 创建一个图形(外容器)并设置其尺寸:

    plt.figure 函数创建一个图形并设置其尺寸(宽度和高度),如下所示。

    代码:

  2. 确定子情节的数量,并为图中的每个子情节分配位置:

    在下面的例子中,我们创建了两个支线剧情,并将它们垂直放置。因此,我们将图形分成两行和一列,每一部分有一个子情节。

    fig.add_subplot 函数创建一个轴对象或子图,并为每个子图分配一个位置。参数–211(用于创建第一个轴对象-“ax1”的 add_subplot 函数)意味着我们给它图中的第一个位置,有两行和一列。

    参数-212(用于创建第二个轴对象- "ax2 "的 add_subplot 函数)意味着我们给出了图中第二个位置的两行和一列。请注意,第一个数字表示行数,第二个数字表示列数,最后一个数字表示子图或轴的位置。

    CODE:

    ax1=fig.add_subplot(211)
    ax2=fig.add_subplot(212)
    
    
  3. 绘制并标注每个支线剧情:

    在位置被分配给每个支线剧情后,我们继续生成单独的支线剧情。我们正在创建一个直方图(使用 hist 函数)和一个条形图(使用条形图函数)。使用 set_xlabelset_ylabel 功能标记 x 轴和 y 轴。

    CODE:

    labelling the x axis
    ax1.set_xlabel("Age")
    #labelling the yaxis
    ax1.set_ylabel("Frequency")
    #plotting a histogram using the hist function
    ax1.hist(df['Age'])
    #labelling the X axis
    ax2.set_xlabel("Category")
    #labelling the Y axis
    ax2.set_ylabel("Numbers")
    #setting the x and y lists for plotting the bar chart
    x=['Males','Females']
    y=[577,314]
    #using the bar function to plot a bar graph
    ax2.bar(x,y)
    
    
fig=plt.figure(figsize=(10,5))

输出:

注意图 7-1 的上半部分被第一个轴对象(直方图)占据,图的下半部分包含第二个子图(柱状图)。

img/498042_1_En_7_Fig1_HTML.jpg

图 7-1

人物中的支线剧情

使用 Pandas 绘图

Pandas 库在幕后使用 Matplotlib 库进行可视化,但是使用 Pandas 函数绘制图形更加直观和用户友好。Pandas 要求数据为宽格式或聚合格式。

Pandas 中使用的 plot 函数(基于 Matplotlib plot 函数)允许我们简单地通过定制指定绘图类型的种类参数的值来创建各种各样的绘图。这也是面向对象编程中多态性的一个例子(OOPS 的原则之一,我们在第二章中研究过),我们使用相同的方法做不同的事情。绘图方法中的种类参数随着您想要绘制的图形种类而变化。

让我们学习如何使用虹膜数据集在 Pandas 身上创建情节。

鸢尾数据集包含鸢尾植物各种物种的样本。每个样本包含五个属性:萼片长度、萼片宽度、花瓣长度、花瓣宽度和种类(鸢尾杂色鸢尾海滨鸢尾)。每个物种有 50 个样本。 Iris 数据集内置于 sklearn 库中,可按如下方式导入:

代码:

import pandas as pd
from sklearn.datasets import load_iris
data=load_iris()
iris=pd.DataFrame(data=data.data,columns=data.feature_names)
iris['species']=pd.Series(data['target']).map({0:'setosa',1:'versicolor',2:'virginica'})

散点图

散点图有助于我们了解两个变量之间是否存在线性关系。要在 Pandas 中生成散点图,我们需要使用值 scatter 和参数 kind ,并在 plot 函数的参数列表中提到要用于绘图的列(由参数“x”和“y”指定)。图 7-2 中的图表表明两个变量(“花瓣长度”和“花瓣宽度”)是线性相关的。

代码:

iris.plot(kind='scatter',x='petal length (cm)',y='petal width (cm)')

输出:

img/498042_1_En_7_Fig2_HTML.jpg

图 7-2

Pandas 散点图

直方图

直方图用于可视化频率分布,不同的条形代表不同区间的频率(图 7-3 )。值“hist”与功能中的种类参数一起用于创建直方图。

代码:

iris['sepal width (cm)'].plot(kind='hist')

输出:

img/498042_1_En_7_Fig3_HTML.jpg

图 7-3

直方图的一个例子

从这个直方图中我们可以看出,“萼片宽度”变量大致呈正态分布。

饼图

饼状图将构成变量的不同值显示为圆中的扇形(图 7-4 )。请注意,Pandas 需要使用 value_counts 函数来计算每个类别中的值的数量,因为在 Pandas 中绘图时,聚合不会自动执行(稍后我们将看到,如果绘图是使用 Seaborn 库完成的,聚合会得到处理)。我们需要使用值“pie”和种类参数来创建饼图。

代码:

iris['species'].value_counts().plot(kind='pie')

输出:

img/498042_1_En_7_Fig4_HTML.jpg

图 7-4

用 Pandas 制作饼图

我们看到这三个物种(“virginica”、“setosa”和“versicolor”)形成了一个圆的相等部分,也就是说,它们具有相同数量的值。

Pandas剧情方法非常直观,容易上手。仅仅通过改变种类参数的值,我们就可以绘制出各种各样的图形。

延伸阅读:查看更多可用于 Pandas 的情节类型:

https://pandas.pydata.org/pandas-docs/stable/user_guide/visualization.html#other-plots

Seaborn 图书馆

Seaborn 是另一个基于 Python 的数据可视化库。Seaborn 更改了 Matplotlib 的默认属性,以调整调色板并对列自动执行聚合。默认设置使得编写创建各种图所需的代码更加容易。

Seaborn 也提供了定制这些图的能力,但是与 Matplotlib 相比,定制选项较少。

Seaborn 使二维以上的数据可视化成为可能。它还要求数据采用长(整齐)格式,这与 Pandas 相反,后者要求数据采用宽格式。

让我们看看如何使用 Seaborn 和 Titanic 数据集绘制图表。

我们使用 Seaborn 中的函数来创建不同的图,以可视化该数据集中的不同变量。

在使用 Seaborn 库的函数之前,需要先导入它。Seaborn 库的别名是 sns ,用于调用绘图函数。

代码:

import seaborn as sns
titanic=pd.read_csv('titanic.csv')

箱线图

基于统计参数,箱线图给出了变量的分布和偏斜度的概念,并指出异常值的存在(用圆圈或圆点表示),如图 7-5 所示。Seaborn 中的箱线图功能可用于创建箱线图。要可视化的特性的列名作为参数传递给该函数。

代码:

sns.boxplot(titanic['Age'])

输出:

img/498042_1_En_7_Fig5_HTML.jpg

图 7-5

展示“年龄”变量的箱线图

向任何 Seaborn 绘图函数添加参数

当我们向 Seaborn 中使用的任何函数传递参数时,我们可以使用两种方法:

  • 我们可以使用完整的列名(包括数据帧的名称),跳过数据参数。

    代码:

  • 或者,以字符串形式提及列名,并使用 data 参数来指定 DataFrame 的名称。

    CODE:

    sns.boxplot(x='Age',data=titanic)
    
    
sns.boxplot(titanic['Age'])

内核密度估计值

核密度估计是一个用于可视化连续变量概率分布的图,如图 7-6 所示。Seaborn 中的 kdeplot 函数用于绘制核密度估计值。

代码:

img/498042_1_En_7_Fig6_HTML.jpg

图 7-6

核密度估计(KDE)图示例

sns.kdeplot(titanic['Age'])

延伸阅读: https://seaborn.pydata.org/generated/seaborn.kdeplot.html

小提琴情节

小提琴图合并了盒图和核密度图,小提琴的形状代表频率分布,如图 7-7 所示。我们使用 Seaborn 中的 violinplot 函数来生成小提琴情节。

代码:

sns.violinplot(x='Pclass',y='Age',data=titanic)

输出:

img/498042_1_En_7_Fig7_HTML.jpg

图 7-7

锡伯恩小提琴情节的一个例子

延伸阅读: https://seaborn.pydata.org/generated/seaborn.violinplot.html

计数图

计数图用于绘制分类变量,条形的长度代表变量的每个唯一值的观察次数。在图 7-8 中,两个条形显示了未幸存的乘客人数(对应于“幸存”变量的值 0)和幸存的乘客人数(对应于“幸存”变量的值 1)。Seaborn 中的计数图函数可用于生成计数图。

代码:

sns.countplot(titanic['Survived'])

输出:

img/498042_1_En_7_Fig8_HTML.jpg

图 7-8

锡伯恩的计数图示例

延伸阅读: https://seaborn.pydata.org/generated/seaborn.countplot.html

热图

热图是关联矩阵的图形表示,代表数据集中不同变量之间的关联,如图 7-9 所示。颜色的强度代表相关性的强度,值代表相关性的程度(接近 1 的值代表两个强相关的变量)。请注意,对角线上的值都是 1,因为它们表示变量与其自身的相关性。

Seaborn 中的热图功能创建热图。参数不能(值为“真”)允许显示代表相关度的值,参数 cmap 可用于改变默认调色板。 corr 方法创建一个数据帧,其中包含不同变量对之间的相关程度。热图的标签由相关数据帧(在本例中为 titanic.corr)中的索引和列值填充。

代码:

img/498042_1_En_7_Fig9_HTML.jpg

图 7-9

Seaborn 的热图示例

sns.heatmap(titanic.corr(),annot=True,cmap='YlGnBu')

延伸阅读:

查看有关“热图”功能及其参数的更多信息:

https://seaborn.pydata.org/generated/seaborn.heatmap.html

查看更多关于自定义调色板和颜色图: https://seaborn.pydata.org/tutorial/color_palettes.html

刻面网格

小平面网格表示单个参数的分布或参数之间的关系穿过包含色调参数的网格,如图 7-10 所示。在第一步中,我们创建一个网格对象(行色调参数是可选的),在第二步中,我们使用这个网格对象绘制我们选择的图形(绘图的名称和要绘制的变量作为参数提供给 map 函数)。Seaborn 中的 FacetGrid 函数用于绘制小平面网格。

示例:代码:

g = sns.FacetGrid(titanic, col="Sex",row='Survived') #Initializing the grid
g.map(plt.hist,'Age')#Plotting a histogram using the grid object

输出:

img/498042_1_En_7_Fig10_HTML.jpg

图 7-10

小平面网格的示例

情节

该图使用线性回归模型在两个连续变量的数据点之间绘制回归线,如图 7-11 所示。Seaborn 函数 regplot 用于创建该图。

代码:

sns.regplot(x='Age',y='Fare',data=titanic)

输出:

img/498042_1_En_7_Fig11_HTML.jpg

图 7-11

regplot 的示例

延伸阅读:

https://seaborn.pydata.org/generated/seaborn.regplot.html#seaborn.regplot

lmplot

该图是 regplot 和 facet grid 的组合,如图 7-12 所示。使用 lmplot 函数,我们可以看到不同参数值的两个连续变量之间的关系。

在下面的例子中,我们绘制了两个数值变量(“年龄”和“费用”),它们跨越了具有不同行和列变量的网格。

代码:

sns.lmplot(x='Age',y='Fare',row='Survived',data=titanic,col='Sex')

输出:

img/498042_1_En_7_Fig12_HTML.png

图 7-12

一个很好的例子

下面总结了 regplot 和 lmplot 之间的差异:

  • regplot 函数只接受两个变量作为参数,而 lmplot 函数接受多个参数。

  • lmplot 函数在图形对象级别工作,而 regplot 函数在轴对象级别工作。

lmplot 上的进一步阅读: https://seaborn.pydata.org/generated/seaborn.lmplot.html

查看更多关于 regplot 和 lmplot 的区别: https://seaborn.pydata.org/tutorial/regression.html#functions-to-draw-linear-regression-models

带状图

带状图类似于散点图。区别在于带状图中使用的变量类型。散点图中两个变量都是连续的,而带状图中一个分类变量对应一个连续变量,如图 7-13 所示。Seaborn 函数带状图生成带状图。

考虑下面的例子,其中“年龄”变量是连续的,而“存活”变量是分类的。

代码:

sns.stripplot(x='Survived',y='Age',data=titanic)

输出:

img/498042_1_En_7_Fig13_HTML.jpg

图 7-13

带状图的示例

延伸阅读: https://seaborn.pydata.org/generated/seaborn.stripplot.html

蜂群图

群图类似于带状图,区别在于群图中的点不像带状图中的点那样重叠。随着点更加分散,我们对连续变量的分布有了更好的了解,如图 7-14 所示。海洋函数蜂群图生成蜂群图。

代码:

sns.swarmplot(x='Survived',y='Age',data=titanic)

输出:

img/498042_1_En_7_Fig14_HTML.jpg

图 7-14

群体图的一个例子

延伸阅读: https://seaborn.pydata.org/generated/seaborn.swarmplot.html#seaborn.swarmplot

猫图

catplot 是带状图和小平面网格的组合。我们可以通过指定色调参数,绘制一个连续变量与各种分类变量的关系,如图 7-15 所示。注意,虽然带状图是由 catplot 函数生成的默认图,但它也可以生成其他图。可使用种类参数改变绘图类型。

代码:

sns.catplot(x='Survived',y='Age',col='Survived',row='Sex',data=titanic)

输出:

img/498042_1_En_7_Fig15_HTML.jpg

图 7-15

Seaborn 的猫图示例

延伸阅读: https://seaborn.pydata.org/generated/seaborn.catplot.html

配对图

配对图显示数据集中所有可能的变量对之间的二元关系,如图 7-16 所示。Seaborn 函数 pairplot 创建一个 pairplot。请注意,您不必提供任何列名作为参数,因为数据集中的所有变量都会被自动视为绘图变量。您需要传递的唯一参数是数据帧的名称。在显示为配对图输出一部分的一些图中,任何给定的变量也相对于其自身绘制。沿着成对图对角线的图显示了这些图。

代码:

sns.pairplot(data=titanic)

输出:

img/498042_1_En_7_Fig16_HTML.jpg

图 7-16

Seaborn 的配对图示例

联合地块

联合图显示了两个变量之间的关系以及变量的个体分布,如图 7-17 所示。 jointplot 函数将待绘制的两个变量的名称作为参数。

代码:

sns.jointplot(x='Fare',y='Age',data=titanic)

输出:

img/498042_1_En_7_Fig17_HTML.jpg

图 7-17

Seaborn 联合地块的一个例子

https://seaborn.pydata.org/generated/seaborn.jointplot.html#seaborn.jointplot 继续阅读

关于 Seaborn 图书馆的进一步阅读:

在 Seaborn 中可以创建的各种图形的例子: https://seaborn.pydata.org/examples/index.html

Seaborn 入门及解决实例: https://seaborn.pydata.org/introduction.html

摘要

  1. 三个基于 Python 的库可用于 Python 中的可视化——Matplotlib(基于 Matlab)、Pandas 和 Seaborn。

  2. 在我们绘制图形之前,我们需要弄清楚需要绘制的变量的类型以及需要绘制的变量的数量。对分类变量使用条形图和饼图,对连续变量使用直方图和散点图。

  3. Matplotlib 有两个用于绘图的接口——有状态接口和面向对象接口。有状态接口使用 pyplot 类,并跟踪这个类的对象的当前状态。面向对象的界面使用对象的层次结构来表示绘图的各种元素,并使用这些对象进行绘图。

  4. Pandas 里用来绘图的 plot 函数,在后端使用 Matplotlib。这个函数通过改变传递给它的参数就可以很容易地绘制任何类型的图形,从而利用了多态原理(一个名称,多种形式)。

  5. Seaborn 是另一个在后端使用 Matplotlib 的库。通过更改默认参数,它最大限度地减少了对图形元素执行聚合、标注和颜色编码的需要。它还提供了可视化两个以上变量的能力。

在下一章中,我们将研究一些真实世界的案例,在这些案例中,我们将把我们所学到的关于可视化和数据争论的知识付诸实践。

复习练习

问题 1

当你有以下变量时,你会用哪个图?

  • 一个分类变量

  • 一个连续变量

  • 两个连续变量

  • 三个或更多连续变量

  • 一个连续变量和一个分类变量

  • 两个连续变量和两个或更多分类变量

问题 2

将左边的功能与右边正确的描述配对

| 1.小平面网格 | a.显示所有可能的变量对和单个变量分布之间关系的图表 | | 2.猫图 | b.穿过分类参数网格的连续变量图 | | 3.群体图 | c.一个连续变量和一个分类变量的图,点不重叠 | | 4.配对图 | d.结合了盒图和核密度估计的图 | | 5.小提琴情节 | e.小平面网格和带状图的组合 |

问题 3

关于使用 Pandas 图书馆进行的可视化,下列哪一项是正确的?

  1. 绘制图形时会自动执行聚合

  2. Pandas 需要长格式的数据

  3. plot 方法用于绘制图形

  4. 类型参数用于指定绘图的类型

  5. 种类参数指定绘图的类型

问题 4

内联显示 Matplotlib 和 Pandas 图形所需的神奇命令是

  1. %matplotlib

  2. 内嵌百分比

  3. %matplotlib inline

  4. 以上都不是

问题 5

axes 对象是指

  1. 横坐标

  2. y 轴

  3. x 轴和 y 轴

  4. 包含图形的子情节

问题 6

对于给定的数据帧 df,我们如何指定热图函数中使用的以下参数?

  1. 相关矩阵

  2. 用于给热图中的方块着色的颜色图

  3. 每个参数之间相关程度的数值

问题 7

Sklearn 库有一个内置的数据集, Iris 。它包含了各种鸢尾属植物的样本。每个样本包含四个属性:萼片长度、萼片宽度、花瓣长度、花瓣宽度、物种(鸢尾杂色鸢尾海滨鸢尾),每个物种 50 个样本。

  • 将此数据集中的数据读入 DataFrame。

  • 创建一个有两个支线剧情的 10*5 图形。

  • 在第一个子图中,画出花瓣的长度和宽度。

  • 在第二个子图中,画出萼片长度对萼片宽度。

  • 对于每个图,标记 x 和 y 轴并设置标题。

问题 8

使用 Seaborn 中的 load_dataset 函数从 tips 数据集(内置于 Seaborn 中)加载数据。该数据集包含总账单金额和访问餐馆的不同顾客的小费值。顾客根据他们的性别、吸烟偏好以及来访的日期和时间进行分类。

  • 创建一个图表,显示吸烟者和不吸烟者总账单的分布(带状图),跨越一个包含不同时间和性别列值的网格。

  • 创建一个绘图来显示“小费”和“总账单”列之间的关系,用于:男性和吸烟者、男性和不吸烟者、女性和吸烟者以及女性和不吸烟者。

答案

问题 1

  • 一个分类变量:计数图

  • 一个连续变量:直方图,核密度估计

  • 两个连续变量:散点图、线图

  • 三个或更多连续变量:热图

  • 一个连续变量和一个分类变量:带状图、群集图、条形图

  • 两个连续变量和两个或更多分类变量:catplot、facet grid、lmplot

问题 2

1-b;2-e;3-c;4-a;五维

问题 3

选项 3 和 5

Pandas 使用带有 kind 参数的 plot 方法来创建各种图形。Pandas 要求数据是广义的或聚合的形式。与 Seaborn 不同,默认情况下不进行聚合。在应用方法之前,聚合需要值计数方法。

问题 4

选项 3

我们使用神奇的命令( %matplotlib inline )在 matplotlib 和 Pandas 中内联显示图形。

问题 5

选项 4

术语“轴”是一个误称,并不是指 x 或 y 轴。它指的是子情节或绘图区域,是人物对象的一部分。一个人物可以包含多个支线剧情。

问题 6

  • 相关矩阵:df.corr()(生成一个表示相关矩阵的数据帧)

  • 用于给热图中的方块着色的颜色图: cmap 参数

  • 表示相关度的数值:annot 参数( annot=True

问题 7

import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
import pandas as pd
#importing the iris dataset
data=load_iris()
iris=pd.DataFrame(data=data.data,columns=data.feature_names)
iris['species']=data.target
iris['species']=iris['species'].map({0:'setosa',1:'versicolor',2:'virginica'})
iris['species'].value_counts().plot(kind='bar')
fig=plt.figure(figsize=(10,5))
ax1=fig.add_subplot(121) #defining the first subplot
#plotting petal length vs petal width in the first subplot
iris.plot(kind='scatter',x='petal length (cm)',y='petal width (cm)',ax=ax1)
#adding the title for the first subplot
ax1.set_title("Petal length vs width")
#adding the label for the X axis
ax1.set_xlabel("Petal length")
#adding the label for the Y axis
ax1.set_ylabel("Petal width")
ax2=fig.add_subplot(122) #defining the second subplot
#plotting sepal length vs sepal width in the second subplot
iris.plot(kind='scatter',x='sepal length (cm)',y='sepal width (cm)',ax=ax2)
ax2.set_xlabel("Sepal length")
ax2.set_ylabel("Sepal width")
ax2.set_title("Sepal length vs width")
#Increasing the distance width between the subplots
fig.subplots_adjust(wspace=1)

问题 8

import seaborn as sns
#loading the data into a DataFrame using the load_dataset function
tips = sns.load_dataset("tips")
#creating a catplot for showing the distribution of the total bill for different combinations of parameters
sns.catplot(x='smoker',y='total_bill',row='time',col='sex',data=tips)
#defining the FacetGrid object and setting the row and column values
g=sns.FacetGrid(tips,row='sex',col='smoker')
#specifying the plot and the columns we want to display
g.map(plt.scatter,'tip','total_bill')

八、数据分析案例研究

在上一章中,我们看了各种基于 Python 的可视化库,以及如何使用这些库中的函数来绘制不同的图形。现在,我们的目标是借助案例研究来理解到目前为止我们所讨论的概念的实际应用。我们研究以下三个数据集:

  • 非结构化数据分析:使用来自一个网页的数据,该网页提供了 2018 年法国票房最高的 50 部电影的信息

  • 空气质量分析:来自新德里(印度)空气质量监测站的数据,提供了四种污染物的每日水平——二氧化硫(SO 2 )、氮氧化物如二氧化氮(NO 2 )、臭氧和细颗粒物(PM 2.5

  • 新冠肺炎趋势分析:获取 2020 年前六个月世界各国每日病例数和死亡数的数据集

技术要求

外部文件

对于第一个案例研究,您需要参考以下 Wikipedia URL(数据直接取自网页):

https://en.wikipedia.org/wiki/List_of_2018_box_office_number-one_films_in_France

对于第二个案例研究,请从以下链接下载一个 CSV 文件:

https://github.com/DataRepo2019/Data-files/blob/master/NSIT%20Dwarka.csv

对于第三个案例研究,从以下链接下载一个 Excel 文件: https://github.com/DataRepo2019/Data-files/blob/master/COVID-19-geographic-disbtribution-worldwide-2020-06-29.xlsx

图书馆

除了我们在前面章节中使用的模块和库(包括 Pandas、NumPy、Matplotlib 和 Seaborn),我们在本章中使用 requests 模块向网站发出 HTTP 请求。

要使用本模块中包含的功能,请使用以下代码行将本模块导入您的 Jupyter 笔记本:

import requests

如果没有安装 requests 模块,您可以在 Anaconda 提示符下使用下面的命令安装它。

pip install requests

方法学

我们将在每个案例研究中使用以下方法:

  1. 打开一个新的 Jupyter 笔记本,并执行以下步骤:

    • 导入分析所需的库和数据

    • 读取数据集并检查前五行(使用 head 方法)

    • 获取关于每列的数据类型和每列中非空值的数量的信息(使用 info 方法)以及数据集的维度(使用 shape 属性)

    • 获取每一列的汇总统计数据(使用 describe 方法),并获取计数、最小值、最大值、标准偏差和百分位数的值

  2. 数据争论

    • 检查列的数据类型是否被正确识别(使用信息数据类型方法)。如果没有,使用 astype 方法改变数据类型

    • 如有必要,使用重命名方法重命名列

    • 使用 drop 方法删除任何不必要或多余的列或行

    • 如果需要,通过使用熔化堆栈方法重组数据,使数据变得整洁

    • 删除任何无关数据(空白值、特殊字符等)。)即不添加任何值,使用替换的方法

    • 使用 isna 方法检查是否存在空值,并使用 dropnafillna 方法删除或填充空值

    • 如果一列能为您的分析增加价值,则添加该列

    • 如果数据为分解格式,则使用 groupby 方法聚合数据

  3. 使用单变量、双变量和多变量图可视化数据

  4. 根据你的分析总结你的见解,包括观察和建议

案例研究 8-1:法国票房最高的电影——分析非结构化数据

在这个案例研究中,数据是从 HTML 页面而不是传统的 CSV 文件中读取的。

我们将要使用的 URL 如下: https://en.wikipedia.org/wiki/List_of_2018_box_office_number-one_films_in_France

该网页有一个表格,显示了 2018 年法国收入排名前 50 的电影的数据。我们使用请求库中的方法在 Pandas 中导入这些数据。Requests 是一个 Python 库,用于发出 HTTP 请求。它有助于从网页中提取 HTML 并与 API 接口。

我们想通过分析回答的问题:

  1. 找出收入最高的五部电影

  2. 排名前十的电影各自的百分比分成(收入)是多少?

  3. 年内月均营收如何变化?

步骤 1:导入数据并检查数据集的特征

首先,导入库并使用必要的函数来检索数据。

代码:

#importing the libraries
import requests
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
#Importing the data from the webpage into a DataFrame
url='https://en.wikipedia.org/wiki/List_of_2018_box_office_number-one_films_in_France'
req=requests.get(url)
data=pd.read_html(req.text)
df=data[0]

我们导入所有的库并将 URL 存储在一个变量中。然后,我们使用 get 方法向这个 URL 发出一个 HTTP 请求,从这个 web 页面中检索信息。requests 对象的 text 属性包含 HTML 数据,该数据被传递给函数 pd.read_html 。该函数返回一个 DataFrame 对象列表,其中包含网页上的各种表格。由于 web 页面上只有一个表,所以 DataFrame (df)只包含一个表。

检查前几条记录:

代码:

df.head()

输出:

img/498042_1_En_8_Figa_HTML.jpg

获取数据类型和缺失值:

代码:

df.info()

输出:

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 50 entries, 0 to 49
Data columns (total 5 columns):
#        50 non-null int64
Date     50 non-null object
Film     50 non-null object
Gross    50 non-null object
Notes    50 non-null object
dtypes: int64(1), object(4)
memory usage: 2.0+ KB

正如我们所看到的,列的数据类型不是我们需要的格式。“毛”列表示总收入,这是一个数字列。但是,该列被分配了一个对象数据类型,因为它包含数字和非数字数据(如“、”和“$”符号等字符,以及“U”和“S”等字母)。下一步,我们处理诸如此类的问题。

第二步:数据争论

在这一步中,我们将:

  1. 删除不必要的字符

  2. 更改数据类型

  3. 删除不需要的列

  4. 从现有列创建新列

让我们从“总值”列中删除不需要的字符串,只保留数值:

代码:

#removing unnecessary characters from the Gross column
df['Gross']=df['Gross'].str.replace(r"US\$","").str.replace(r",","")

在前面的语句中,我们使用一系列链式替换方法和正则表达式原则来替换非数字字符。第一个 replace 方法删除“US$ ”,第二个 replace 方法删除逗号。用空字符串("")替换字符相当于删除字符。

现在,让我们使用 astype 方法将该列的数据类型转换为 int64 ,以便该列可以用于计算和可视化:

代码:

#changing the data type of the Gross column to make the column numeric
df['Gross']=df['Gross'].astype('int64')

为了检查这些更改是否已经反映出来,我们检查该列的前几条记录并验证数据类型:

代码:

df['Gross'].head(5)

输出:

0     6557062
1     2127871
2     2006033
3     2771269
4    16604101
Name: Gross, dtype: int64

正如我们从输出中看到的,该列的数据类型已经更改,值不再包含字符串。

我们还需要提取日期的月份部分,我们将首先更改“date”列的数据类型,然后对其应用 DatetimeIndex 方法,如下所示。

代码:

#changing the data type of the Date column to extract its components
df['Date']=df['Date'].astype('datetime64')
#creating a new column for the month
df['Month']=pd.DatetimeIndex(df['Date']).month

最后,我们使用下面的语句从数据帧中删除两个不必要的列。

代码:

#dropping the unnecessary columns
df.drop(['#','Notes'],axis=1,inplace=True)

第三步:可视化

为了可视化我们的数据,首先我们创建另一个 DataFrame (df1 ),它包含原始 DataFrame (df)包含的列的子集。这个数据帧 df1 只包含两列——“电影”(电影的名字)和“总收入”(总收入)。然后,我们按降序对收入值进行排序。这显示在下面的步骤中。

代码:

df1=df[['Film','Gross']].sort_values(ascending=False,by='Gross')

有一个不需要的列(“索引”)被添加到这个数据帧中,我们将在下一步中删除它。

代码:

df1.drop(['index'],axis=1,inplace=True)

前五名电影:s

我们创建的第一个图是一个条形图,显示了收入排名前五的电影:(图 8-1 )。

#Plotting the top 5 films by revenue
#setting the figure size
plt.figure(figsize=(10,5))
#creating a bar plot
ax=sns.barplot(x='Film',y='Gross',data=df1.head(5))
#rotating the x axis labels
ax.set_xticklabels(labels=df1.head()['Film'],rotation=75)
#setting the title
ax.set_title("Top 5 Films per revenue")
#setting the Y-axis labels
ax.set_ylabel("Gross revenue")
#Labelling the bars in the bar graph
for p in ax.patches:
ax.annotate(p.get_height(),(p.get_x()+p.get_width()/2,p.get_height()),ha='center',va='bottom')

输出:

img/498042_1_En_8_Fig1_HTML.jpg

图 8-1

显示前五部电影各自份额百分比的饼图

为了描述前十部电影的份额(按收入),我们创建了一个饼图(图 8-2 )。

代码:

#Pie chart showing the share of each of the top 10 films by percentage in the revenue
df1['Gross'].head(10).plot(kind='pie',autopct='%.2f%%',labels=df1['Film'],figsize=(10,5))

输出:

img/498042_1_En_8_Fig2_HTML.jpg

图 8-2

描绘前十部电影收入份额的饼图

我们首先创建另一个 DataFrame,通过计算每个月的平均值来聚合一个月的数据(图 8-3 )。

代码:

#Aggregating the revenues by month
df2=df.groupby('Month')['Gross'].mean()
#creating a line plot
df2.plot(kind='line',figsize=(10,5))

输出:

img/498042_1_En_8_Fig3_HTML.jpg

图 8-3

2018 年平均票房月收入(法国)

步骤 4:基于分析和可视化得出推论

  1. 平均月收入差异很大,可能取决于电影上映的月份,这可能需要跨年度的进一步分析。

  2. 2018 年法国票房最高的三部电影是复仇者联盟家族Les Tuche 3

案例研究 8-2:利用数据分析进行空气质量管理

为了监测环境空气质量的状况,印度中央污染控制委员会(CPCB)在印度各地建立了一个庞大的监测站网络。定期监测的参数包括二氧化硫(SO 2 )、氮氧化物如二氧化氮(NO 2 )、臭氧、细颗粒物(PM 2.5 )。根据多年来的趋势,国家首都德里的空气质量已经成为公众关注的问题。接下来对每日空气质量数据进行逐步分析,以展示数据分析如何帮助规划空气质量管理中的干预措施。

注意:用于本案例研究的数据集的名称是:“Note 德瓦尔卡. csv ”.,请参考技术描述部分,了解如何导入该数据集的详细信息。

我们想通过分析回答的问题:

  1. 年平均水平:在四种污染物中,SO 2 、NO 2 、臭氧和 PM 2.5 ,它们的年平均水平经常超过规定的年度标准?

  2. 每日标准:对于关注的污染物,每年有多少天超过每日标准?

  3. 时间变化:哪些月份的污染水平在大多数日子里超过临界水平?

步骤 1:导入数据并检查数据集的特征

代码:

import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
#aqdata is the name of the DataFrame, short for Air Quality Data.
aqdata=pd.read_csv('NSIT Dwarka.csv')
aqdata.head()

输出:

img/498042_1_En_8_Figb_HTML.jpg

检查 : 列的数据类型

代码:

aqdata.info()

输出:

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 2192 entries, 0 to 2191
Data columns (total 6 columns):
From Date    2191 non-null object
To Date      2191 non-null object
PM2.5        2191 non-null object
SO2          2187 non-null object
Ozone        2187 non-null object
NO2          2190 non-null object
dtypes: object(6)
memory usage: 102.8+ KB

观察:尽管 SO 2 、NO 2 、臭氧和 PM 2.5 的值是数字,但 Pandas 将这些列的数据类型读取为"对象"。为了使用这些列(例如,绘制图表、观察趋势、计算聚合值),我们需要更改这些列的数据类型。此外,似乎还有一些遗漏的条目。

第二步:数据争论

根据上一步中的观察,在这一步中,我们将

  1. 处理缺失值:我们可以选择删除空值或替换空值。

  2. 更改列的数据类型。

检查数据集中缺失的值:

代码:

aqdata.isna().sum()

输出:

From Date    1
To Date      1
PM2.5        1
SO2          5
Ozone        5
NO2          2
dtype: int64

似乎没有很多缺失的价值,但这就是问题所在。当我们使用 head 语句检查前几行时,我们看到一些丢失的值在原始数据集中被表示为 None 。然而,Pandas 并不认为这些是空值。让我们用值 np.nan 替换值 None ,这样 Pandas 就可以将这些值视为空值:

代码:

aqdata=aqdata.replace({'None':np.nan})

现在,如果我们计算空值的数量,我们会看到一个非常不同的画面,表明数据集中缺失值的出现率要高得多。

代码:

aqdata.isna().sum()

输出:

From Date      1
To Date        1
PM2.5        562
SO2           84
Ozone        106
NO2          105
dtype: int64

让我们检查列的当前数据类型:

代码:

aqdata.info()

输出:

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 2192 entries, 0 to 2191
Data columns (total 6 columns):
From Date    2191 non-null object
To Date      2191 non-null object
PM2.5        1630 non-null object
SO2          2108 non-null object
Ozone        2086 non-null object
NO2          2087 non-null object
dtypes: object(6)
memory usage: 102.8+ KB

我们看到包含数值的列没有被识别为数值列,包含日期的列也没有被正确识别。具有不正确数据类型的列成为下一步的障碍,在下一步中,我们分析趋势并绘制图表;这一步要求列的数据类型采用适合 Pandas 阅读的格式。

在下面的代码行中,我们使用 pd.to_datetime 方法将“开始日期”和“结束日期”列的数据类型转换为 datetime 类型,这样可以更容易地分析日期的各个组成部分,如月和年。

代码:

aqdata['From Date']=pd.to_datetime(aqdata['From Date'], format='%d-%m-%Y %H:%M')
aqdata['To Date']=pd.to_datetime(aqdata['To Date'], format='%d-%m-%Y %H:%M')
aqdata['SO2']=pd.to_numeric(aqdata['SO2'],errors='coerce')
aqdata['NO2']=pd.to_numeric(aqdata['NO2'],errors='coerce')
aqdata['Ozone']=pd.to_numeric(aqdata['Ozone'],errors='coerce')
aqdata['PM2.5']=pd.to_numeric(aqdata['PM2.5'],errors='coerce')

使用 info 方法检查数据类型是否已经改变。

代码:

aqdata.info()

输出:

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 2192 entries, 0 to 2191
Data columns (total 6 columns):
From Date    2191 non-null datetime64[ns]
To Date      2191 non-null datetime64[ns]
PM2.5        1630 non-null float64
SO2          2108 non-null float64
Ozone        2086 non-null float64
NO2          2087 non-null float64
dtypes: datetime64ns, float64(4)
memory usage: 102.8 KB

由于我们的大部分分析考虑的是年度数据,我们创建了一个新列来提取年份,使用的是 pd。DatetimeIndex 函数。

代码:

aqdata['Year'] = pd.DatetimeIndex(aqdata['From Date']).year

现在,我们为每年创建单独的 DataFrame 对象,以便我们可以每年分析数据。

代码:

#extracting the data for each year
aq2014=aqdata[aqdata['Year']==2014]
aq2015=aqdata[aqdata['Year']==2015]
aq2016=aqdata[aqdata['Year']==2016]
aq2017=aqdata[aqdata['Year']==2017]
aq2018=aqdata[aqdata['Year']==2018]
aq2019=aqdata[aqdata['Year']==2019]

现在,让我们看看每年数据中的空值数量:

代码:

#checking the missing values in 2014
aq2014.isna().sum()

输出:

From Date      0
To Date        0
PM2.5        365
SO2            8
Ozone          8
NO2            8
Year           0
dtype: int64

#checking the missing values in 2015
aq2015.isna().sum()

输出:

From Date      0

To Date        0
PM2.5        117
SO2           12
Ozone         29
NO2           37
Year           0
dtype: int64

代码:

#checking the missing values in 2016
aq2016.isna().sum()

输出:

From Date     0
To Date       0
PM2.5        43
SO2          43
Ozone        47
NO2          42
Year          0
dtype: int64

#checking the missing values in 2017
aq2017.isna().sum()

输出:

From Date     0
To Date       0
PM2.5        34
SO2          17
Ozone        17
NO2          12
Year          0
dtype: int64

代码:

#checking the missing values in 2018
aq2018.isna().sum()

输出:

From Date    0
To Date      0
PM2.5        2
SO2          2
Ozone        2
NO2          2
Year         0
dtype: int64

代码:

#checking the missing values in 2019

aq2019.isna().sum()

输出:

From Date    0
To Date      0
PM2.5        0
SO2          1
Ozone        2
NO2          3
Year         0
dtype: int64

从对每年的空值的分析中,我们看到 2014 年和 2015 年的数据具有大部分缺失值。因此,我们选择忽略 2014 年和 2015 年的数据,并分析 2016 年至 2019 年的 4 年数据。根据印度中央污染控制委员会制定的标准,我们需要至少 104 个每日监测值才能得出年平均值。

2016 年、2017 年、2018 年和 2019 年是分析空气质量数据的四年。在进入下一步之前,我们删除 2016 年至 2019 年每年缺失的值,而不是替换它们,因为我们有这四年中每一年的足够数据(超过 104 个读数)来计算年平均值,如下所示。

代码:

#dropping the null values for the four years chosen for analysis
aq2016.dropna(inplace=True) # inplace=True makes changes in the original dataframe
aq2017.dropna(inplace=True)
aq2018.dropna(inplace=True)
aq2019.dropna(inplace=True)

第三步:数据可视化

分析的第一部分:绘制污染物的年平均值

根据监测到的 PM 2.5 、SO 2 、NO 2 和臭氧(O 3 的 24 小时平均环境空气浓度,绘制年平均值,以识别年平均值超过规定的国家环境空气质量标准的参数。

首先,我们计算每种污染物(PM 2.5 ,SO 2 ,NO 2 ,臭氧)的年平均值,如下所示:

代码:

#Yearly averages for SO2 in each year
s16avg=round(aq2016['SO2'].mean(),2)
s17avg=round(aq2017['SO2'].mean(),2)
s18avg=round(aq2018['SO2'].mean(),2)
s19avg=round(aq2019['SO2'].mean(),2)
#Yearly averages for PM2.5 in each year
p16avg=round(aq2016['PM2.5'].mean(),2)
p17avg=round(aq2017['PM2.5'].mean(),2)
p18avg=round(aq2018['PM2.5'].mean(),2)
p19avg=round(aq2019['PM2.5'].mean(),2)
#Yearly averages for NO2 in each year
n16avg=round(aq2016['NO2'].mean(),2)
n17avg=round(aq2017['NO2'].mean(),2)
n18avg=round(aq2018['NO2'].mean(),2)
n19avg=round(aq2019['NO2'].mean(),2)

说明:代表污染物平均值的变量命名符号如下:污染物的第一个字母,年份,平均值的缩写“avg”。例如,s15avg 表示 2015 年 SO 2 的平均水平。我们使用均值方法计算平均值,并使用 round 函数将平均值四舍五入到小数点后两位。我们不考虑臭氧,因为年度标准不适用于臭氧。

接下来,我们为每种污染物创建一个数据框架,每个数据框架有两列。其中一列表示年份,另一列显示该年的年平均水平。

代码:

#Creating data frames with yearly averages for each pollutant
dfs=pd.DataFrame({'Yearly average':[s16avg,s17avg,s18avg,s19avg]},index=['2016','2017','2018','2019']) #dfs is for SO2
dfp=pd.DataFrame({'Yearly average':[p16avg,p17avg,p18avg,p19avg]},index=['2016','2017','2018','2019']) #dfp is for PM2.5
dfn=pd.DataFrame({'Yearly average':[n16avg,n17avg,n18avg,n19avg]},index=['2016','2017','2018','2019']) #dfn is for NO2

现在,我们准备绘制每种污染物年平均值的图表(图 8-4 )。

代码:

#Creating a figure with 3 subplots - 1 for each pollutant
fig,(ax1,ax2,ax3)=plt.subplots(1,3)
#Creating a DataFrame the yearly averages for NO2
dfn.plot(kind='bar',figsize=(20,5),ax=ax1)
#Setting the title for the first axes object
ax1.set_title("NO2", fontsize=18)
#Setting the X-axis label for the NO2 graph
ax1.set_xlabel("Years", fontsize=18)
ax1.legend().set_visible(False)
#Setting the Y-axis label
ax1.set_ylabel("Yearly average", fontsize=18)
#Creating a dashed line to indicate the annual standard
ax1.hlines(40, -.9,15, linestyles="dashed")
#Labelling this dashed line
ax1.annotate('Annual avg. standard for NO2',(-0.5,38))
#labelling the bars
for p in ax1.patches:
    ax1.annotate(p.get_height(),(p.get_x()+p.get_width()/2,p.get_height()), color="black", ha="left", va ='bottom',fontsize=12)

#Plotting the yearly averages similarly for PM2.5
dfp.plot(kind='bar',figsize=(20,5),ax=ax2)
ax2.set_title("PM2.5", fontsize=18)
ax2.hlines(40, -.9,15, linestyles="dashed")
ax2.annotate('Annual avg. standard for PM2.5',(-0.5,48))
ax2.legend().set_visible(False)
for p in ax2.patches:
    ax2.annotate(p.get_height(),(p.get_x()+p.get_width()/2,p.get_height()), color="black", ha="center", va ='bottom',fontsize=12)

#Plotting the yearly averages similarly for SO2

dfs.plot(kind='bar',figsize=(20,5),ax=ax3)
ax3.hlines(50, -.9,15, linestyles="dashed")
ax3.annotate('Annual avg. standard for SO2',(-0.5,48))
ax3.set_title("SO2", fontsize=18)
ax3.legend().set_visible(False)
for p in ax3.patches:
    ax3.annotate(p.get_height(),(p.get_x()+p.get_width()/2,p.get_height()), color="black", ha="center", va ='bottom',fontsize=12)

输出:

img/498042_1_En_8_Fig4_HTML.jpg

图 8-4

污染物(NO 2 ,PM 2.5 ,SO 2 )相对于其年平均标准的年平均水平

观察:很明显,仅 PM 2.5 超过年平均值标准。对于 NO 2 ,观测值比较接近规定标准。对于 SO 2 ,观测值远小于年标准。因此,为了进一步分析,只考虑这两种污染物(NO 2 和 PM 2.5 )。

空气质量分析第二部分:绘制 PM 2.5 和 NO 2 每年 24 小时超标天数

虽然分析的第一步指出了空气质量管理和干预规划所关注的污染物,但在第二步中,我们显示了每年 24 小时数值超过标准的各种水平是如何分布的。在 PM 2.5 的情况下,我们绘制了每年观察值落在以下范围内的天数。

  1. 0 至 60 微克/立方米 3

  2. 61 至 120 微克/立方米 3

  3. 121 至 180 微克/立方米 3

  4. 180 μg/m 3

为了绘制这些数据,我们需要为 2016 年到 2019 年的每一年创建 DataFrame 对象,以捕获 PM 2.5 水平落在这些间隔中的天数,如下所示:

代码:

#Creating intervals for 2016 with the number of days with PM2.5 concentration falling in that interval
a2=aq2016[(aq2016['PM2.5']<=60)]['PM2.5'].count()
b2=aq2016[((aq2016['PM2.5']>60) & (aq2016['PM2.5']<=120))]['PM2.5'].count()
c2=aq2016[((aq2016['PM2.5']>120) & (aq2016['PM2.5']<=180))]['PM2.5'].count()
d2=aq2016[(aq2016['PM2.5']>180)]['PM2.5'].count()
dfpb2016=pd.DataFrame({'year':'2016','pm levels':['<60','between 61 and 120','between 121 and 180','greater than 180'],'number of critical days':[a2,b2,c2,d2]})
#Creating intervals for 2017 with the number of days with PM2.5 concentration falling in each interval
a3=aq2017[(aq2017['PM2.5']<=60)]['PM2.5'].count()
b3=aq2017[((aq2017['PM2.5']>60) & (aq2017['PM2.5']<=120))]['PM2.5'].count()
c3=aq2017[((aq2017['PM2.5']>120) & (aq2017['PM2.5']<=180))]['PM2.5'].count()
d3=aq2017[(aq2017['PM2.5']>180)]['PM2.5'].count()
dfpb2017=pd.DataFrame({'year':'2017','pm levels':['<60','between 61 and 120','between 121 and 180','greater than 180'],'number of critical days':[a3,b3,c3,d3]})
#Creating intervals for 2018 with the number of days with PM2.5 concentration falling in each interval

a4=aq2018[(aq2018['PM2.5']<=60)]['PM2.5'].count()
b4=aq2018[((aq2018['PM2.5']>60) & (aq2018['PM2.5']<=120))]['PM2.5'].count()
c4=aq2018[((aq2018['PM2.5']>120) & (aq2018['PM2.5']<=180))]['PM2.5'].count()
d4=aq2018[(aq2018['PM2.5']>180)]['PM2.5'].count()
dfpb2018=pd.DataFrame({'year':'2018','pm levels':['<60','between 61 and 120','between 121 and 180','greater than 180'],'number of critical days':[a4,b4,c4,d4]})
#Creating intervals for 2019 with the number of days with PM2.5 concentration falling in each interval
a5=aq2019[(aq2019['PM2.5']<=60)]['PM2.5'].count()
b5=aq2019[((aq2019['PM2.5']>60) & (aq2019['PM2.5']<=120))]['PM2.5'].count()
c5=aq2019[((aq2019['PM2.5']>120) & (aq2019['PM2.5']<=180))]['PM2.5'].count()
d5=aq2019[(aq2019['PM2.5']>180)]['PM2.5'].count()
dfpb2019=pd.DataFrame({'year':'2019','pm levels':['<60','between 61 and 120','between 121 and 180','greater than 180'],'number of critical days':[a5,b5,c5,d5]})

现在,我们用这些时间间隔为每年绘制一个堆积条形图。为此,我们需要创建如下数据透视表:

代码:

dfpivot2019=dfpb2019.pivot(index='year',columns='pm levels',values='number of critical days')
dfpivot2018=dfpb2018.pivot(index='year',columns='pm levels',values='number of critical days')
dfpivot2017=dfpb2017.pivot(index='year',columns='pm levels',values='number of critical days')
dfpivot2016=dfpb2016.pivot(index='year',columns='pm levels',values='number of critical days')

使用这些数据透视表,我们创建如下堆叠条形图(图 8-5 ):

代码:

#Creating a figure with 4 sub-plots, one for each year from 2016-19
fig,(ax1,ax2,ax3,ax4)=plt.subplots(1,4)
fig.suptitle("Number of days per year in each interval")
cmp=plt.cm.get_cmap('RdBu')
#Plotting stacked horizontal bar charts for each year to represent intervals of PM2.5 levels
dfpivot2019.loc[:,['<60','between 61 and 120','between 121 and 180','greater than 180']].plot.barh(stacked=True, cmap=cmp,figsize=(15,5),ax=ax1)
dfpivot2018.loc[:,['<60','between 61 and 120','between 121 and 180','greater than 180']].plot.barh(stacked=True, cmap=cmp, figsize=(15,5),ax=ax2)
dfpivot2017.loc[:,['<60','between 61 and 120','between 121 and 180','greater than 180']].plot.barh(stacked=True, cmap=cmp, figsize=(15,5),ax=ax3)
dfpivot2016.loc[:,['<60','between 61 and 120','between 121 and 180','greater than 180']].plot.barh(stacked=True, cmap=cmp, figsize=(15,5),ax=ax4)
#Setting the properties - legend, yaxis and title
ax1.legend().set_visible(False)
ax2.legend().set_visible(False)
ax3.legend().set_visible(False)
ax4.legend(loc='center left',bbox_to_anchor=(1,0.5))
ax1.get_yaxis().set_visible(False)
ax2.get_yaxis().set_visible(False)
ax3.get_yaxis().set_visible(False)
ax4.get_yaxis().set_visible(False)
ax1.set_title('2019')
ax2.set_title('2018')
ax3.set_title('2017')
ax4.set_title('2016')

输出:

img/498042_1_En_8_Fig5_HTML.jpg

图 8-5

PM 2.5 的每个间隔级别每年的天数

观察:

可以看出,每年都观察到 PM 2.5 值超过 180 μg/m 3 ,因此,首先,对包括交通在内的主要污染活动的限制可以局限于此类别。

2 逐区间标图

同样,对于 NO 2 ,每年监测值超过 80 微克/立方米 3 的 24 小时标准的天数被绘制出来(图 8-6 )。

首先,我们为编号为 2 的项目创建一个数据框,该数据框捕捉每年中数值高于 80 μg/m 的天数 3 ,如下所示。

代码:

#Calculating the number of days in each year with regard to critical days of NO2 concentration
a=aq2015[(aq2015['NO2']>=80)]['NO2'].count()
b=aq2016[(aq2016['NO2']>=80)]['NO2'].count()
c=aq2017[(aq2017['NO2']>=80)]['NO2'].count()
d=aq2018[(aq2018['NO2']>=80)]['NO2'].count()
e=aq2019[(aq2019['NO2']>=80)]['NO2'].count()
dfno=pd.DataFrame({'years':['2015','2016','2017','2018','2019'],'number of days with NO2>80 μg':[a,b,c,d,e]})
ax=dfno.plot(kind='bar',figsize=(10,5))
ax.set_xticklabels(['2015','2016','2017','2018','2019'])
ax.set_title("NO2 number of days in each year with critical levels of concentration")
for p in ax.patches:
    ax.annotate(p.get_height(), (p.get_x() + p.get_width() / 2, p.get_height()), ha = 'center', va = 'bottom')

输出:

img/498042_1_En_8_Fig6_HTML.jpg

图 8-6

每年 NO 2 浓度达到临界水平的天数

推断:观察到的 24 小时 NO 2 值在五年中只有三年出现超标。

由于观察到的 24 小时 NO 2 值仅轻微超标,且仅持续几天,下一步仅限于对 PM 2.5 的进一步分析。

空气质量分析的第三部分:确定 PM 2.5 日值在大多数日子里超过临界水平的月份

在对车辆交通和施工等显著增加环境 PM 2.5 浓度的活动实施限制之前,有必要提供足够的通知,以避免对公众造成不便。因此,对于明显高于 180 微克/立方米的每日 PM 2.53 ,我们绘制了一年中每个月的时间变化图。为了做到这一点,在 12 个月中的每个月,我们捕捉每年 24 小时 PM 2.5 值超过 180 微克/立方米 3 的严重空气污染天数。

首先,我们用每月 PM 2.5 值超过 180 微克/立方米 3 的天数为每年创建数据帧,如下所示。

代码:

#Creating a dataframe for 2016 with the number of days in each month where the PM2.5 concentration is >180
aq2016['Month']=pd.DatetimeIndex(aq2016['From Date']).month #extracting the month
aq2016['condition']=(aq2016['PM2.5']>=180 ) # creating a boolean column that is True when the PM2.5 value is greater than 180 and false when it is less than 180
aq2016['condition']=aq2016['condition'].replace({False:np.nan}) # replacing the False values with null values, so that the count method in the next statement only counts the True values or the values corresponding to PM 2.5>180
selection1=aq2016.groupby('Month')['condition'].count() #Using the groupby method to calculate the number of days for each month that satisfy the condition(PM2.5>180)
#Repeating the above process for 2017, creating a dataframe with the number of days in each month where the PM2.5 concentration is >180
aq2017['Month']=pd.DatetimeIndex(aq2017['From Date']).month
aq2017['condition']=(aq2017['PM2.5']>=180 )
aq2017['condition']=aq2017['condition'].replace({False:np.nan})
selection2=aq2017.groupby('Month')['condition'].count()
#Repeating the above process for 2018, creating a dataframe with the number of days in each month where the PM2.5 concentration is >180
aq2018['Month']=pd.DatetimeIndex(aq2018['From Date']).month
aq2018['condition']=(aq2018['PM2.5']>=180 )
aq2018['condition']=aq2018['condition'].replace({False:np.nan})
selection3=aq2018.groupby('Month')['condition'].count()
#Repeating the above process for 2019, creating a dataframe with the number of days in each month where the PM2.5 concentration is >180
aq2019['Month']=pd.DatetimeIndex(aq2019['From Date']).month
aq2019['condition']=(aq2019['PM2.5']>=180 )
aq2019['condition']=aq2019['condition'].replace({False:np.nan})
selection4=aq2019.groupby('Month')['condition'].count()

现在,我们将所有 DataFrame 对象连接成一个对象(我们称之为“selectionc”),以获得 PM2.5>180μg/m3的每月天数的综合图,如下所示。

代码:

#selectionc data frame is a consolidated dataframe showing month-wise critical values of PM2.5 for every year
selectionc=pd.concat([selection1,selection1,selection3,selection4],axis=1)
#renaming the columns
selectionc.columns=['2016','2017','2018','2019']
selectionc

输出:

img/498042_1_En_8_Figc_HTML.jpg

从该表中我们可以看出,第 1 个月(1 月)、第 11 个月(11 月)和第 12 个月(12 月)是四年中最关键的月份,因为这些月份的 PM 2.5 > 180 微克/立方米 3 的天数最多。

现在我们已经有了所有的数据,让我们使用下面的代码来可视化 PM 2.5 (图 8-7 )的关键日子。

代码:

#creating a bar chart representing number of days with critical levels of PM2.5(>180) concentrations
ax=selectionc.plot(kind='bar',figsize=(20,7),width=0.7,align='center',colormap='Paired')
bars = ax.patches
#creating patterns to represent each year
patterns =('-','x','/','O')
#ax.legend(loc='upper left', borderpad=1.5, labelspacing=1.5)
ax.legend((patterns),('2016','2017','2018','2019'))
hatches = [p for p in patterns for i in range(len(selectionc))]
#setting a pattern for each bar
for bar, hatch in zip(bars, hatches):
    bar.set_hatch(hatch)
#Labelling the months, the X axis and Y axis
ax.set_xticklabels(['Jan','Feb','Mar','Apr','May','June','July','Aug','Sept','Oct','Nov','Dec'],rotation=30)
ax.set_xlabel('Month',fontsize=12)
ax.set_ylabel('Number of days with critical levels of PM2.5',fontsize=12)
#Labelling the bars
for i in ax.patches:
    ax.text(i.get_x()-.003, i.get_height()+.3,
            round(i.get_height(),2), fontsize=10,
                color='dimgrey')
ax.legend()
ax.set_title("Number of days with critical levels of PM2.5 in each month of years 2016-19")

输出:

img/498042_1_En_8_Fig7_HTML.jpg

图 8-7

PM2.5–每年每月达到临界水平的天数

步骤 4:基于分析和可视化得出推论

从上图可以看出,大多数严重污染的日子出现在 1 月、11 月和 12 月。因此,根据过去四年记录的 PM 2.5 的日平均浓度,可能会在 1 月、11 月和 12 月对车辆交通、施工活动、柴油泵组的使用、从邻国进入德里的交通分流以及其他类似活动实施限制。为了对整个德里做出这样的决定,分析来自其他监测站的数据也是必要的。传播数据和分析上述内容将有助于人们提前为限制做好准备,并理解这些措施背后的基本原理。

前面演示的方法使用数据分析作为辅助空气质量管理的工具,使用位于德里 Netaji Subhas 技术研究所(NSIT)的一个监测站记录的数据。这一方法可以按照以下思路进行。

  1. 对第 2 号重复上述步骤,以显示占大部分天数的关键月份,其中无 2 记录值超过 24 小时标准。做这个练习将再次帮助确定面临两个参数污染水平的月份,PM 2.5 和 NO 2 和计划。

  2. 利用 NSIT 空气质量监测站的数据和其他监测站的类似数据,重复进行分析,以便可以对整个德里的干预措施进行规划。

案例研究 8-3:全球新冠肺炎案例分析

该数据集包含截至 2020 年 6 月 29 日新冠肺炎病例的地理分布数据(来源:欧洲疾病控制中心,来源 URL: https://www.ecdc.europa.eu/en/publications-data/download-todays-data-geographic-distribution-covid-19-cases-worldwide )。请注意,此链接包含最新数据,但我们使用的是 6 月 29 日的数据(本章开头的“技术要求”部分提供了数据集的链接)。

我们希望通过分析回答的问题包括:

  1. 哪些国家的死亡率最高,病例最多,死亡人数最多?

  2. 自疫情开始以来,每月的病例和死亡人数趋势如何?

  3. 在一些国家,强制实行封锁是为了帮助拉平曲线。这项措施有助于减少病例数吗?

步骤 1:导入数据并检查数据集的特征

使用 pd.read_excel 函数读取数据集并检查前五行(使用 head 方法):

代码:

df=pd.read_excel('COVID-19-geographic-distribution-worldwide-2020-06-29.xlsx')
df.head()

输出:

img/498042_1_En_8_Figd_HTML.jpg

获取关于每列的数据类型和每列中非空值的数量的信息(使用 info 方法)。

代码:

df.info()

输出:

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 26562 entries, 0 to 26561
Data columns (total 11 columns):
dateRep                    26562 non-null datetime64[ns]
day                        26562 non-null int64
month                      26562 non-null int64
year                       26562 non-null int64
cases                      26562 non-null int64
deaths                     26562 non-null int64
countriesAndTerritories    26562 non-null object
geoId                      26455 non-null object
countryterritoryCode       26498 non-null object
popData2019                26498 non-null float64
continentExp               26562 non-null object
dtypes: datetime64ns, float64(1), int64(5), object(4)
memory usage: 2.2+ MB

获取每一列的汇总统计数据(使用 describe 方法)并获取 count、min、max、standard deviation 和 percentiles 的值:

代码:

df.describe()

输出:

img/498042_1_En_8_Fige_HTML.jpg

第二步:数据争论

在这一步中,我们将:

  • 检查列的数据类型是否被准确识别。如果没有,更改数据类型:从 info 方法的输出中,我们看到所有列的数据类型都被正确地识别了。

  • 如有必要,重命名列:在下面的代码中,我们将重命名 DataFrame 的列。

代码:

  • 删除任何不必要的列或行:

    我们看到国家代码列在数据帧中出现了两次(具有两个不同的名称:“旧国家代码”和“国家代码”),因此我们删除了其中一列(“旧国家代码”):

#changing the column names
df.columns=['date','day','month','year','cases','deaths','country','old_country_code','country_code','population','continent']

代码:

  • 删除不会增加任何价值的无关数据:
#Dropping the redundant column name
df.drop(['old_country_code'],axis=1,inplace=True)

该数据帧中没有空格、特殊字符或任何其他无关字符。我们看到只有 2019 年 12 月一天的数据;因此,我们删除了本月的数据,并为剩余的 11 个月创建了一个新的数据框架(df1)。

代码:

  • 使用 isnaisnull 方法检查是否有空值,如果有,则采用适当的方法处理:
df1=df[df.month!=12]

计算空值的百分比:

代码:

df1.isna().sum().sum()/len(df1)

输出:

0.008794112096622004

由于空值的百分比小于 1%,我们在下面的步骤中删除空值。

代码:

  • 如果数据为分解格式,则汇总数据:
df1.dropna(inplace=True)

这个数据帧中的数据不是聚合格式,我们在这一步中使用 groupby 方法将其转换成这种格式。我们可以按国家、洲或日期分组。让我们按国名分组。

代码:

#Aggregating the data by country name
df_by_country=df1.groupby('country')['cases','deaths'].sum()
df_by_country

输出(仅显示前九行):

img/498042_1_En_8_Figf_HTML.jpg

前面的输出显示了每个国家的病例和死亡人数的综合情况。

让我们在这个汇总数据框架中添加另一列——死亡率,它是死亡人数与病例数的比率。

代码:

#Adding a new column for the mortality rate which is the ratio of the number of deaths to cases
df_by_country['mortality_rate']=df_by_country['deaths']/df_by_country['cases']

第三步:可视化数据

在本案例研究的第一个可视化中,我们使用数据框架“df_by_country”中的聚合数据来显示死亡率最高的二十个国家(图 8-8 )。

代码:

#Sorting the values for the mortality rate in the descending order
plt.figure(figsize=(15,10))
ax=df_by_country['mortality_rate'].sort_values(ascending=False).head(20).plot(kind='bar')
ax.set_xticklabels(ax.get_xticklabels(), rotation=45, ha="right")
for p in ax.patches:
    ax.annotate(p.get_height().round(2),(p.get_x()+p.get_width()/2,p.get_height()),ha='center',va='bottom')
ax.set_xlabel("Country")
ax.set_ylabel("Mortality rate")
ax.set_title("Countries with highest mortality rates")

输出:

img/498042_1_En_8_Fig8_HTML.jpg

图 8-8

描述新冠肺炎死亡率最高的国家的条形图

在第二个视图中,我们使用饼图显示了新冠肺炎病例数量最多的十个国家,如图 8-9 所示。

代码:

#Pie chart showing the countries with the highest number of COVID cases
df_cases=df_by_country['cases'].sort_values(ascending=False)
ax=df_cases.head(10).plot(kind='pie',autopct='%.2f%%',labels=df_cases.index,figsize=(12,8))
ax.set_title("Top ten countries by case load")

输出:

img/498042_1_En_8_Fig9_HTML.jpg

图 8-9

按新冠肺炎案例描绘前十个国家/地区份额的饼图

在接下来的可视化中,我们使用条形图(图 8-10 )找出新冠肺炎疫情造成的人员伤亡最严重的五个国家。

代码:

#sorting the number of deaths in the descending order
plt.figure(figsize=(10,6))
ax=df_by_country['deaths'].sort_values(ascending=False).head(5).plot(kind='bar')
ax.set_xticklabels(ax.get_xticklabels(), rotation=45, ha="right")
for p in ax.patches:
    ax.annotate(p.get_height(),(p.get_x()+p.get_width()/2,p.get_height()),ha='center',va='bottom')
ax.set_title("Countries suffering the most fatalities from COVID-19")
ax.set_xlabel("Countries")
ax.set_ylabel("Number of deaths")

输出:

img/498042_1_En_8_Fig10_HTML.jpg

图 8-10

描述死亡人数最多的五个国家的条形图

现在,我们绘制线形图来观察新冠肺炎病例和死亡人数的月趋势。

为了绘制折线图,我们首先按月汇总数据,然后并排绘制两个折线图,显示病例数和死亡数,如图 8-11 所示。

代码:

df_by_month=df1.groupby('month')['cases','deaths'].sum()
fig=plt.figure(figsize=(15,10))
ax1=fig.add_subplot(1,2,1)
ax2=fig.add_subplot(1,2,2)
df_by_month['cases'].plot(kind='line',ax=ax1)
ax1.set_title("Total COVID-19 cases across months in 2020")
ax1.set_xlabel("Months in 2020")
ax1.set_ylabel("Number of cases(in million)")
df_by_month['deaths'].plot(kind='line',ax=ax2)
ax2.set_title("Total COVID-19 deaths across months in 2020")
ax2.set_xlabel("Months in 2020")
ax2.set_ylabel("Number of deaths")

输出:

img/498042_1_En_8_Fig11_HTML.jpg

图 8-11

锁定对平坦曲线的影响

许多国家实施了封锁,以遏制病例增加的趋势,并使曲线变平。我们现在来看看四个国家——印度、英国、意大利和德国——在三月份实施了封锁,看看这一措施是否达到了预期的效果。

首先,我们为每个国家创建 DataFrame 对象,按月汇总数据。

代码:

#Creating DataFrames for each country
#Monthwise aggregated data for Germany
df_germany=df1[df1.country=='Germany']
df_germany_monthwise=df_germany.groupby('month')['cases','deaths'].sum()
df_germany_grouped=df_germany_monthwise.reset_index()
#Monthwise aggregated data for UK
df_uk=df1[df1.country=='United_Kingdom']
df_uk_monthwise=df_uk.groupby('month')['cases','deaths'].sum()
df_uk_grouped=df_uk_monthwise.reset_index()
#Monthwise aggregated data for India
df_india=df1[df1.country=='India']
df_india_monthwise=df_india.groupby('month')['cases','deaths'].sum()
df_india_grouped=df_india_monthwise.reset_index()
#Monthwise aggregated data for Italy
df_italy=df1[df1.country=='Italy']
df_italy_monthwise=df_italy.groupby('month')['cases','deaths'].sum()
df_italy_grouped=df_italy_monthwise.reset_index()

现在,我们使用在前面步骤中创建的 DataFrame 对象来绘制这些国家的折线图,以查看 2020 年各月的病例数,如图 8-12 所示。

代码:

#Plotting the data for four countries (UK, India, Italy and Germany) where lockdowns were imposed
fig=plt.figure(figsize=(20,15))
ax1=fig.add_subplot(2,2,1)
df_uk_grouped.plot(kind='line',x='month',y='cases',ax=ax1)
ax1.set_title("Cases in UK across months")
ax2=fig.add_subplot(2,2,2)
df_india_grouped.plot(kind='line',x='month',y='cases',ax=ax2)
ax2.set_title("Cases in India across months")
ax3=fig.add_subplot(2,2,3)
df_italy_grouped.plot(kind='line',x='month',y='cases',ax=ax3)
ax3.set_title("Cases in Italy across months")
ax4=fig.add_subplot(2,2,4)
df_germany_grouped.plot(kind='line',x='month',y='cases',ax=ax4)
ax4.set_title("Cases in Germany across months")

输出:

img/498042_1_En_8_Fig12_HTML.jpg

图 8-12

2020 年前 6 个月英国、印度、德国和意大利的总病例数

步骤 4:基于分析和可视化得出推论

  • 病例数:美国、巴西、俄罗斯、印度和英国的病例数最高。

  • 死亡人数:美国、巴西、英国、意大利和法国的死亡人数最高。

  • 死亡率:也门、圣马丁、法国、比利时和意大利的死亡率最高。

  • 趋势:

    • 病例总数一直在稳步增加,而死亡总数在 4 月份后有所下降。

    • 封锁的影响:我们分析了四个国家——印度、英国、德国和意大利——三月份实施了封锁。除印度之外,所有这些国家在实施封锁后,病例都出现了总体下降。在英国和德国,病例最初有所上升(在封锁的早期阶段),但在这次高峰之后开始下降。

摘要

  • 在本章中,我们查看了从结构化和非结构化数据源导入数据的各种案例研究。Pandas 支持从多种格式中读取数据。

  • requests 模块的功能使我们能够向 web 页面发送 HTTP 请求,并将页面内容存储在对象中。

  • 对案例进行典型的描述性或探索性数据分析,首先是构建我们希望通过分析回答的问题,并找出如何导入数据。在此之后,我们获得了关于数据的更多信息——各列的含义、度量单位、缺失值的数量、不同列的数据类型等等。

  • 数据争论是描述性或探索性数据分析的关键,在数据争论中,我们准备、清理和组织数据以使其适合分析。典型的活动包括删除无关数据、处理空值、重命名列、聚合数据和更改数据类型。

  • 一旦数据准备好并适合于分析,我们就使用 Matplotlib、Seaborn 和 Pandas 等库来可视化我们的数据,以帮助我们获得能够回答我们最初提出的问题的见解。

复习练习

问题 1(小型案例研究)

考虑下面网页上的第一个表格: https://en.wikipedia.org/wiki/Climate_of_South_Africa 。它包含南非各城市夏季和冬季的最高和最低温度(以摄氏度为单位)的数据。

  • 使用 requests 模块中的适当方法向该 URL 发送一个 get 请求,并将该页面上第一个表中的数据存储在 Pandas DataFrame 中。

  • 将列重命名为:“城市”、“夏季(最大值)”、“夏季(最小值)”、“冬季(最大值)”和“冬季(最小值)”。

  • 将“Winter(min)”列第一行的负值替换为 0,并将该列的数据类型转换为 int64

  • 绘制一个图表,显示南非夏季最热的城市(使用 Summer(max)列)。

  • 绘制一个图表,显示南非冬季最冷的城市(使用冬季(分钟)列)。

问题 2

十名雇员(缩写为 A–J)的周薪如下:100,120,80,155,222,400,199,403,345,290。将每周工资存储在数据框架中。

  • 绘制一个条形图,以降序显示工资

  • 使用注释方法标记条形图中的每个条形

问题 3

| 1.用于发送 HTTP 请求的模块 | a.网页的 URL | | 2.*获取*的方法 | b.请求文本 | | 3.传递给 *get* 方法的参数 | c.使用给定的 URL 获取信息 | | 4.包含 Unicode 内容的属性 | d.要求 |

问题 4

read_html Pandas 函数读取

  1. 网页上的所有 HTML 内容

  2. 网页中的 HTML 标签

  3. 作为 DataFrame 对象列表的所有 HTML 表

  4. 网页中的 HTML 列表

答案

问题 1

代码:

import requests
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
%matplotlib inline
url='https://en.wikipedia.org/wiki/Climate_of_South_Africa'
#making a get request to the URL
req = requests.get(url)
#storing the HTML data in a DataFrame
data = pd.read_html(req.text)
#Reading the first table
df=data[0]
#Renaming the columns
df.columns=['City','Summer(max)','Summer(min)','Winter(max)','Winter(min)']
#Replacing the negative value with 0
df['Winter(min)']=df['Winter(min)'].str.replace(r"–2","0")
#Changing the data type from object to int64
df['Winter(min)']=df['Winter(min)'].astype('int64',errors='ignore')
#Using the city as the index to facilitate plotting
df1=df.set_index('City')
#Hottest five cities during Summer
df1['Summer(max)'].sort_values(ascending=False).head(5).plot(kind='bar')
#Coldest five cities during Winter
df1['Winter(min)'].sort_values().head(5).plot(kind='bar')

问题 2

代码:

numbers=pd.Series([100,120,80,155,222,400,199,403,345,290])
#converting the data to a DataFrame
numbers.to_frame()
#labelling the index
numbers.index=list('ABCDEFGHIJ')
#labelling the column
numbers.columns=['Wages']
ax=numbers.sort_values(ascending=False).plot(kind='bar')
#labelling the bars
for p in ax.patches:
    ax.annotate(p.get_height(),(p.get_x()+p.get_width()/2,p.get_height()),ha='center',va='bottom')

问题 3

一维;2-c;3-a;4-b

问题 4

选项 3

网页上每个表格的内容都存储在单独的 DataFrame 对象中。

九、使用 Python 实现统计和概率

在前一章中,我们学习了如何通过解决一些案例研究来应用您的数据分析知识。

现在,在这本书的最后一部分,我们将学习统计和概率中的基本概念,并了解如何用 Python 解决统计问题。我们涵盖的主题包括排列和组合、概率、概率规则和贝叶斯定理、概率分布、集中趋势的度量、离差、偏度和峰度、抽样、中心极限定理和假设检验。我们还会查看置信水平、显著性水平、p 值、假设检验、参数检验(单样本和双样本 z 检验、单样本和双样本 t 检验、配对检验、方差分析[ANOVA])和非参数检验(卡方检验)。

排列和组合

让我们看一些定义、公式和例子,它们将帮助我们理解排列和组合的概念。

组合:我们可以选择一组对象的各种方式。

下面的公式给出了我们可以从给定数量的物体中形成的组合数量:

$$ n{c}_r=\frac{n!}{r!\left(n-r\right)!} $$

在前面的公式中, n 是从中抽取对象的较小子集的集合中对象的总数, c 是组合的数目, r 是子集中对象的数目。感叹号(!)表示一个数的阶乘。比如 x!是从 1 到 x 的所有整数的乘积。

(x!=x*(x-1) (x-2) …。1)

现在让我们解决一个涉及组合的简单问题。

问题:找出总共五种口味的冰淇淋圣代中包含三种口味的方法。

答:设五味为 A、B、C、D、e,手工解出这道题,可以得到以下组合:

  • A,B,C|B,C,D|A,C,D | B,D|C,D,E|B,D,E|A,B,E|A,B,E|A,D,E | A,C,E | E

如我们所见,有十种组合。如果我们应用 nc r 公式,其中 n 为 5, r 为 3,我们得到相同的答案(5 C 3 = 10)。

现在让我们看看什么是排列。

排列类似于组合,但在这里,对象排列的顺序很重要。

下面的公式给出了排列的个数:

$$ {n}_{P_r}=\frac{n!}{\left(n-r\right)!} $$

考虑同样的冰淇淋例子,让我们看看我们能得到多少种排列,也就是说,从总共五种口味中选择和排列三种口味的方式的数量。

  1. ABC|CBA|BCA|ACB|CAB|BAC

  2. BCD|CDB|BDC|CBD|DBC|DCB

  3. ACD|ADC|DAC|DCA|CAD|CDA

  4. ABD|ADB|BAD|BDA|DAB|DBA

  5. CDE|CED|DCE|DEC|ECD|EDC

  6. bde | bed | dbe | deb |同上|EDB

  7. ABE|AEB|BEA|BAE|EAB|EBA

  8. ADE|AED|DAE|DEA|EAD|EDA

  9. ACE|AEC|CAE|CEA|EAC|ECA

  10. BCE|BEC|CBE|CEB|EBC|ECB

正如我们所看到的,我们可以得到每种组合的六种可能的排列。因此,排列总数= 10*6 = 60。公式$$ {n}_{P_r} $$(其中 n =5、 r =3)也给出了同样的答案(60)。

解决涉及排列问题的另一种方法如下:

  1. 首先选择物品:用 5 种C3 方式从五种口味中选择三种口味

  2. 现在安排 3 中的三个项目!方法

  3. 将步骤 1 和步骤 2 的结果相乘。排列总数= 5C3∫3!= 60

现在我们已经理解了排列和组合的概念,让我们看看概率的本质。

可能性

下面给出几个与概率相关的重要概念。

随机实验:这是导致确定结果的任何程序,可以在相同条件下重复任何次数。

结果:单一实验的结果。

样本空间:包含实验所有可能结果的详尽列表。

事件:事件可以包含单个结果或多个结果的组合。事件是样本空间的子集。

概率:对事件可能性的定量度量。任何事件的概率总是介于 0 和 1 之间。0 表示事件不可能发生,1 表示事件一定会发生。

如果字母 X 表示我们的事件,那么概率由符号 P(X)= N(X)/N(S)给出

其中 N(X)=事件 X 中的结果数

N(S)=样本空间中结果的总数

求解示例:概率

下面是一个简单的概率问题。

问题:一个实验中,一个骰子滚动两次。求两次投掷所得数字相加为 10 的概率。

解决方案:

事件 A:掷出第一个骰子。

事件 B:掷出第二个骰子。

样本空间:一个骰子包含 1 到 6 这几个数字,它们出现的概率是相等的。当掷出一个骰子时,结果的总数是六。因为事件“A”和“B”是独立的,所以这两个事件的结果总数= 6*6 = 36。

事件 X:两个数之和为 10。导致这个结果的可能结果包括{4,6}、{6,4}和{5,5 };也就是说,三种可能的结果的总和是 10。

p(X)=获得和的概率 10 =事件中的结果数 X/总样本空间=3/36=0.0833

概率规则

让我们理解概率的各种规则,在表 9-1 中解释。

表 9-1

概率规则

|

规则

|

描述

|

公式

|

维恩图

|
| --- | --- | --- | --- |
| 加法法则 | 加法法则决定了两个事件发生的概率。 | P (A U B) = P(A)+P(B)-P (A ∩ B) | img/498042_1_En_9_Figa_HTML.gif |
| 特殊加法规则 | 这条规则适用于互斥事件。互斥事件是那些不能同时发生的事件。对于互斥事件,其中任何一个事件发生的概率就是每个事件发生的概率之和。 | P(A U B)= P(A)+P(B) | img/498042_1_En_9_Figb_HTML.gif |
| 乘法法则 | 乘法规则是提供两个事件同时发生的概率的一般规则。 | P(A ∩ B)=P(A)P(B/A)P(B/A)是在事件 A 已经发生的情况下,事件 B 发生的条件概率*。 | img/498042_1_En_9_Figc_HTML.gif |
| 特殊乘法法则 | 这条规则适用于独立事件。对于独立事件,事件一起发生的概率仅仅是事件概率的乘积。 | 页:1 | img/498042_1_En_9_Figd_HTML.gif |

注意,表 9-1 中列出的公式提供了两个事件的规则,但是这些规则可以扩展到任何数量的事件。

条件概率

条件概率包括在考虑到已经发生的另一个事件的概率之后,计算一个事件的概率。考虑图 9-1 ,它说明了条件概率的原理。

img/498042_1_En_9_Fig1_HTML.jpg

图 9-1

条件概率

图 9-1 表明,如果事件“A”依赖于事件“B”,那么样本空间就是事件“B”本身。例如,假设客户从在线零售商处购买了一件产品。设事件的概率为 0.5。,或者换句话说,P(A)=0.5。

现在,假设 B 是客户打算购买的产品收到负面评价的事件。由于这种负面评价,客户购买该产品的可能性现在可能比以前小。假设现在他们购买该产品的可能性只有 30%。换句话说,P(A/B) =顾客在收到负面评价的情况下购买产品的概率= 0.3。

条件概率的公式是 P(A/B) =P (A ∩ B)/P(B)。

贝叶斯定理

贝叶斯定理是在给定一些与事件相关的证据的情况下,用于计算事件的条件概率的定理。贝叶斯定理在事件的概率和与之相关的条件的先验知识之间建立了一种数学关系。随着与事件相关的证据的积累,可以更准确地确定该事件的概率。

涉及贝叶斯定理的问题不同于常规的概率问题,我们一般是计算未来可能发生的事件的概率。例如,在一个简单的条件概率问题中,我们可能被要求计算一个人患糖尿病的概率,假设他们是肥胖的。在贝叶斯定理中,我们回溯并计算一个人肥胖的概率,假设他们患有糖尿病。也就是说,如果一个人的糖尿病测试呈阳性,贝叶斯定理将检验他是否肥胖的假设。贝叶斯定理的公式如下:

P(A/B)=$$ \frac{P\left(B/A\right)\ast P(A)}{P(B)} $$

P(A/B),也叫后验概率,是我们要计算的,也就是给定手头的数据,假设为真的概率。

P(B/A)是在给定假设的情况下,获得证据的概率。

P(A)是先验概率,即在我们有任何与之相关的数据之前,假设为真的概率。

P(B)是证据出现的一般概率,没有任何假设,也称为归一化常数

贝叶斯定理的应用:下面给出了贝叶斯定理可以应用的几个领域。

  • 医学诊断学

  • 财务和风险评估

  • 垃圾邮件分类

  • 执法和犯罪侦查

  • 投机

现在,让我们通过几个例子来理解贝叶斯定理在这些领域中的实际应用。

贝叶斯定理在医学诊断学中的应用

贝叶斯定理在医学诊断领域有应用,这可以借助下面的假设例子来理解。

问:考虑一个场景,一个人的疾病测试呈阳性。已知这种疾病在任何给定时间都会影响大约 1.2%的人口。对于患有这种疾病的人来说,这种诊断测试的准确率为 85%。对于没有患病的人来说,测试的准确率是 97%。假设这个人的测试结果是阳性,那么他患这种疾病的可能性有多大?

解决方案:

评估医学检验的准确性是贝叶斯定理的应用之一。

让我们首先定义这些事件:

答:人有疾病,也叫假设。

~答:人没有病。

b:这个人的疾病检测呈阳性,也叫证据。

P(A/B):假设这个人的测试结果为阳性,那么这个人患病的概率,也称为后验概率(这是我们需要计算的)。

P(B/A):假设患者患有该疾病,则该患者检测呈阳性的概率。该值为 0.85(如问题中给出的,该测试对患有该疾病的人具有 85%的准确性)。

P(A): 先验概率或者这个人患病的概率,没有任何证据(比如医学测试)。该值为 0.012(如问题中给出的,该疾病影响 1.2%的人口)。

P(B):这个人的这种疾病检测呈阳性的概率。这个概率可以用下面的方式计算。

这个人有两种方法可以检测出阳性:

  • 他们患有该疾病,并且测试呈阳性(真阳性)——这种情况发生的概率可以计算如下:

    P(B/A)P(A)=0.850.012=0.0102。

  • 他们没有患病,但测试不准确,他们的测试结果呈阳性(假阳性)——发生这种情况的概率可以计算如下:

    P(B/~ A)* P(~ A)=(1-0.97)*(1-0.012)= 0.02964。

    在这里,P(B/~A)指的是对于没有患病的人来说测试呈阳性的概率,即对于没有患病的人来说测试不准确的概率。

    由于这项测试对没有患这种疾病的人有 97%的准确性,它对 3%的病例不准确。

    换句话说,P(B/~A) = 1-0.97。

    同样,P(~A)指的是这个人没有患病的概率。由于我们得到的数据表明这种疾病的发病率为 1.2%,P(~A)为= 1-0.012。

贝叶斯定理方程中的分母 P(B)是前面两个概率的并集=(P(B/A)* P(A))+(P(B/~ A)* P(~ A))= 0.0102+0.2964 = 0.03984

我们现在可以通过在贝叶斯定理公式中插入分子和分母的值来计算我们的最终答案。

P(A/B)= P(B/A)* P(A)/P(B)= 0.85 * 0.012/0.03984 = 0.256

使用贝叶斯定理,我们现在可以得出结论,即使医学测试呈阳性,此人也只有 25.6%的机会患此病。

贝叶斯定理的另一个应用:垃圾邮件分类

让我们看看贝叶斯定理在垃圾邮件分类领域的另一个应用。在垃圾邮件过滤器时代之前,没有办法将未经请求的电子邮件与合法邮件分开。人们不得不手动筛选电子邮件来识别垃圾邮件。如今,垃圾邮件过滤器已经自动完成这项任务,并且在识别垃圾邮件和只保留垃圾邮件方面非常有效。贝叶斯方法形成了许多垃圾邮件过滤器背后的原理。考虑下面的例子:

问题:假设一封邮件包含单词“offer”,那么它是垃圾邮件的概率有多大?现有数据表明,50%的电子邮件是垃圾邮件。9%的垃圾邮件包含“提议”一词,0.4 %的垃圾邮件包含“提议”一词

回答:

将事件和概率定义如下:

答:电子邮件是“垃圾邮件”

~答:邮件是“火腿”

b:邮件里有“offer”这个词

P(A) = 0.5(假设 50%的电子邮件是垃圾邮件)

P(B/A) =包含单词“offer”的垃圾邮件的概率= 0.09 (9%)

P(B/~A) =包含单词“offer”的火腿邮件的概率= 0.004 (0.4%)

应用贝叶斯定理:

p(A/B)=(0.09 * 0.5)/(0.09 * 0.5)+(0.004)*(0.5)= 0.957

换句话说,给定具有单词“offer”的邮件是垃圾邮件的概率是 0.957。

科学图书馆

Scipy 是一个用于数学和科学计算的库,它包含了许多领域的函数和算法,包括图像处理、信号处理、聚类、微积分、矩阵和统计。在 SciPy 中,每个区域都有单独的子模块。我们在本章中使用了 scipy.stats 子模块,并将该子模块中的函数应用于统计测试和不同类型的分布。该模块还包含距离计算、相关性和列联表的功能。

延伸阅读:

阅读更多关于 scipy.stats 模块及其功能的信息:

https://docs.scipy.org/doc/scipy/reference/stats.html

概率分布

为了理解概率分布,让我们先来看看随机变量的概念,它是用来模拟概率分布的。

随机变量:其值等于与随机实验结果相关的数值的变量。

随机变量有两种类型:

  1. 离散随机变量可以取有限的、可数的值。例如,用于调查和问卷以评估回答的李克特量表的随机变量可以取值为 1、2、3、4 和 5。与离散随机变量相关的概率质量函数PMF 是一个函数,它提供该变量恰好等于某个离散值的概率。

  2. 连续随机变量可以取无限多的值。例子包括温度、身高和体重。对于连续变量,我们无法找到绝对概率。因此,我们对连续变量使用概率密度函数,或 PDF (相当于离散变量的 PMF)。PDF 是连续随机变量的值落在一个值范围内的概率。

    累积分布函数(CDF)给出了随机变量小于或等于给定值的概率。它是 PDF 的积分,给出了 PDF 定义的曲线下某一点的面积。

    在下一节中,我们将介绍离散随机变量的两种概率分布:二项式和泊松分布。

二项分布

在二项式实验中,有几个独立的试验,每个试验只有两种可能的结果。这些结果是二项离散随机变量的两个值。二项分布的一个基本例子是反复投掷硬币。每次投掷只有两种结果:正面或反面。

以下是二项式分布的特征:

  1. n 个相同的试验

  2. 每次试验都只有两种可能的结果

  3. 一项试验的结果不会影响其他试验的结果

  4. 每次试验的成功概率( p )和失败概率( q )是相同的

  5. 随机变量代表在这些 n 次试验中的成功次数,并且最多可以等于 n

  6. 二项式分布的均值和方差如下:

    平均值= np* (试验次数*成功概率)

    方差= npq (试验次数成功概率失败概率)

PMF,或在实验的 n 次尝试中 r 成功的概率,由以下等式给出:

p(X = r)=nCrprqn-r

其中 p 为成功概率, q 为失败概率, n 为试验次数

二项式分布的形状

二项式分布类似于偏态分布,但随着 n 增加和 p 变小,它更接近对称,看起来像正态曲线,如图 9-2 所示。

img/498042_1_En_9_Fig2_HTML.jpg

图 9-2

不同值的二项式分布

问题:地铁公司对乘坐地铁的八位老年人进行了调查,询问他们对地铁引入的新安全功能的满意度。每个回答只有两个值:是或否。让我们假设基于历史调查,回答“是”的概率是 0.6,回答“否”的概率是 0.4。

计算…的概率

  1. 正好有三个人对地铁的新安全功能感到满意

  2. 不到五个人满意

解决方案:

  1. 对于问题的第一部分:我们可以使用公式nCrprqn-r或者使用 Scipy 函数( stats.binom.pmf )来求解,如下所示:

    CODE:

    import scipy.stats as stats
    n,r,p=8,3,0.6
    stats.binom.pmf(r,n,p)
    
    

    输出:

    0.12386304000000009
    
    

    说明:首先需要导入 scipy.stats 模块。然后我们定义三个变量——n(试验次数) r (成功次数) p (失败概率)。之后,调用二项式分布的 PMF(stats.binom.pdf),我们依次传递三个参数- rnp 。使用 pmf 函数是因为我们正在计算一个离散变量的概率。

  2. 对于问题的第二部分:由于我们需要计算少于五个人满意的概率,变量的极限值是 4。

    下面的等式给出了我们需要计算的概率:

    P(X < = 4)= P(X = 0)+P(X = 1)+P(X = 2)+P(X = 3)+P(X = 4)

    我们可以应用公式nCrprqn-r来计算 r = 0、1、2、3 和 4 的值,或者使用 Scipy 中的 stats.binom.cdf 函数求解,如下所示:

    CODE:

    import scipy.stats as stats
    n,r,p=8,4,0.6
    stats.binom.cdf(r,n,p)
    
    

    Output:

    0.40591359999999976
    
    

    说明:当我们计算 x 的多个值的概率时,我们使用 CDF 函数。

泊松分布

泊松分布是对给定间隔(通常是时间间隔,但也可以是距离、面积或体积的间隔)内发生的事件数量进行建模的分布。需要知道事件的平均发生率。

泊松分布的 PMF 由以下等式给出:

$$ P\left(x=r\right)=\frac{\lambdar{e}{-\lambda }}{r!} $$

其中 P(x=r)是事件发生 r 次的概率,r 是事件发生的次数, λ r 表示该事件发生的平均/预期次数。

泊松分布可用于计算给定时间段内发生的次数,例如:

  • 每小时到达餐厅的人数

  • 工厂一年内发生的工伤事故数量

  • 呼叫中心一周内的客户投诉数量

泊松分布的性质:

  1. 均值=方差= λ 。在泊松分布中,平均值和方差具有相同的数值。

  2. 这些事件是独立的、随机的,不能同时发生。

  3. n 为> 20 并且 p 为< 0.1 时,泊松分布可以近似为二项式分布。这里我们用 λ = np 代替。

  4. n 的值较大, p 在 0.5 左右, np > 0.5 时,可以用正态分布来近似二项分布。

泊松分布的形状

泊松分布在形状上是偏斜的,但随着均值( λ )的增加,开始类似正态分布,如图 9-3 所示。

img/498042_1_En_9_Fig3_HTML.jpg

图 9-3

泊松分布

泊松分布求解示例:

在地铁站,平均有两台自动售票机停止工作。假设停止运行的机器数量遵循泊松分布,计算给定时间点的概率:

  1. 正好有三台机器停止运转

  2. 两台以上的机器停止运转

解决方案:

  1. 我们可以使用公式$$ \frac{\lambdar{e}{-\lambda }}{r!} $$或者用 Python 求解,如下所示:

    CODE:

    import scipy.stats as stats
    λ=2
    r=3
    stats.poisson.pmf(r,λ)
    
    

    输出:

    0.18044704431548356
    
    

    说明:首先需要导入 scipy.stats 模块。然后我们定义两个变量——λ(平均值)和 r (事件发生的次数)。然后,泊松分布的 PMF(stats . Poisson . PMF)被调用,我们将两个参数传递给这个函数, rλ ,按此顺序。

  2. 由于我们需要计算两台以上机器发生故障的概率,因此我们需要计算以下概率:

    P(x>2),或者(1-p(x=0)-p(x=1)-p(x=2))。

    这可以使用 stats.poisson.cdf 函数来计算,其中 r=2。

    CODE:

    import scipy.stats as stats
    λ=2
    r=2
    1-stats.poisson.cdf(r,λ)
    
    

    Output:

    0.3233235838169366
    
    

解释:我们遵循与问题第一部分类似的方法,但是使用 CDF 函数( stats.poisson.cdf )而不是 PMF ( stats.poisson.pmf )。

连续概率分布

有几种连续的概率分布,包括正态分布、学生的 T 分布、卡方分布和方差分析分布。在下一节中,我们将探讨正态分布。

正态分布

正态分布是一条对称的钟形曲线,由其均值( μ )和标准差( σ )定义,如图 9-4 所示。

img/498042_1_En_9_Fig4_HTML.jpg

图 9-4

正态分布

图 9-4 中的四条曲线都是正态分布。平均值用符号 μ (mu)表示,标准偏差用符号 σ (sigma)表示

正态分布的特征

  1. 中心值( μ )也是正态分布的众数和中位数

  2. Checking for normality: In a normal distribution, the difference between the 75th percentile value (Q3) and the 50th percentile value (median or Q2) equals the difference between the median (Q2) and the 25th percentile. In other words,

    $$ {Q}_3-{Q}_2={Q}_2-{Q}_1 $$

如果分布是偏斜的,这个等式就不成立。

在右偏分布中,(Q3-Q2)>(Q2-Q1)

在左偏分布中,(Q2-Q1)>(Q3-Q2)

标准正态分布

为了标准化单位并比较具有不同均值和方差的分布,我们使用标准正态分布。

标准正态分布的性质:

  • 标准正态分布是平均值为 0,标准差为 1 的正态分布。

  • 使用以下公式可以将任何正态分布转换为标准正态分布:

    z= $$ \frac{\left(x-\mu \right)}{\sigma } $$,其中μ和σ为原正态分布的均值和方差。

  • 在标准的正态分布中,

    • 68.2%的值位于平均值的 1 个标准偏差内

    • 95.4%的值位于平均值的两个标准偏差之间

    • 99.8%位于平均值的 3 个标准偏差内

数值分布如图 9-5 所示。

img/498042_1_En_9_Fig5_HTML.jpg

图 9-5

标准正态分布

  • 任意两点之间的标准正态分布下的面积表示位于这两点之间的值的比例。例如,平均值两侧曲线下的面积为 0.5。换句话说,50%的值位于平均值的两边。

有两种类型的问题涉及正态分布:

  1. 计算与变量值相对应的概率/比例:使用公式 z= $$ \frac{\left(x-\mu \right)}{\sigma } $$计算 z 值,然后将该 z 值作为参数传递给 stats.norm.cdf 函数

  2. 计算给定概率对应的变量的值:首先,通过将概率值作为参数传递给 stats.norm.ppf 函数来获得 z 值。然后,我们通过在下面的公式中代入值来获得对应于 z 值的变量(x)的值:z= $$ \frac{\left(x-\mu \right)}{\sigma } $$

求解实例:标准正态分布

问题:一家软件公司的 IT 团队正在检查一些笔记本电脑。团队需要选择前 1%的笔记本电脑,标准是最快的启动时间。平均启动时间为 7 秒,标准偏差为 0.5 秒。选择所需的截止启动时间是多少?

解决方案:

步骤 1:由于标准是快速启动时间,感兴趣的启动时间位于分布的左下端,如图 9-6 所示。

img/498042_1_En_9_Fig6_HTML.jpg

图 9-6

低尾检验(标准正态分布)

这条尾巴右边的曲线面积是 0.99。我们使用 stats.norm.ppf 函数计算 z 值,对应于概率值 0.99:

代码:

stats.norm.ppf(0.99)

输出:

2.3263478740408408

由于这是一个低尾检验,我们将 z 的值取为–2.33(该值为负,因为它位于平均值的左侧)。我们还可以使用 z 表通过计算对应于概率 0.99 的 z 值来验证这一点。

第二步:应用下面的公式并计算 x

z=(x-μ)/σ

其中 z = -2.33, μ = 7, σ = 0.5。我们需要计算 x 的值:

代码:

x=(-2.33*0.5)+7

产量:5.835

推论:所需的引导时间是 5.835 秒

示例 2(标准正态分布):

一家公司生产灯管,这些灯管的寿命(以小时计)遵循平均值为 900 小时、标准偏差为 150 小时的正态分布。计算以下内容:

  1. 在最初的 750 小时内失败的管状灯的比例

  2. 在 800 到 1100 小时之间失效的灯管比例

  3. 多少小时后 20%的灯管灯会失效?

示例 2 的解决方案(标准正态分布):

  1. 计算 X=750 对应的 z 值,并获得相应的概率:

    CODE:

    x=750
    μ=900
    σ=150
    z=(x-μ)/σ
    z
    stats.norm.cdf(z)
    
    

    Output:

    0.15865525393145707
    
    

    推论:15.8%的灯管灯在最初的 750 小时内出现故障。

  2. 分别计算 x 值 800 和 1100 对应的 z 值,并减去这些 z 值对应的概率。

    CODE:

    x1=800
    x2=1100
    μ=900
    σ=150
    z1=(x1-μ)/σ
    z2=(x2-μ)/σ
    p2=stats.norm.cdf(z2)
    p1=stats.norm.cdf(z1)
    p2-p1
    
    

    输出:

    0.6562962427272092
    
    

    推断:大约 65.6%的灯管寿命在 800 到 1100 小时之间,会发生故障。

  3. 计算对应于概率 0.2 的 z 值,并通过在公式 z= $$ \frac{\left(x-\mu \right)}{\sigma } $$中代入 z、 μσ 来计算 x

    CODE:

    z=stats.norm.ppf(0.2)
    μ=900
    σ=150
    x=μ+σ*z
    x
    
    

    输出:

    773.7568149640629
    
    

    推论:在大约 774 小时的寿命后,20%的灯管会失效。

集中趋势测量

集中趋势是对数据集中一组值的中心值的度量。以下是集中趋势的一些衡量标准:

  • Mean :这是数据集中值的平均值。

  • Median :这是数据集中的值按大小排列时的中间数。

  • Mode :离散值数据集中出现频率最高的值。

  • 百分比:百分比是低于特定值的值的百分比的度量。中位数对应于第 50 百分位。

  • 四分位数:四分位数是将有序数据集中的值分成四个相等组的值。Q1(或第一个四分位数)对应于第 25 个百分点,Q2 对应于中间值,Q3 对应于第 75 个百分点。

离差的度量

离差的度量给出了分布范围的定量度量。它们提供了分布中的值是位于中心值周围还是分散的概念。以下是常用的离差度量。

  • 范围:范围是数据集中最低值和最高值之差的度量。

  • 四分位数间距:衡量第三个四分位数和第一个四分位数之间的差异。这种方法受极端值的影响较小,因为它关注的是位于中间的值。四分位间距是有异常值的偏斜分布的一个很好的度量四分位距由 IQR = Q3 - Q1 表示。

  • 方差:这是对数据集中有多少值分散在平均值周围的度量。方差的值可以很好地表明平均值是否代表数据集中的值。较小的方差将表明平均值是集中趋势的适当度量。下面的公式给出了方差:

    $$ {\sigma}²=\frac{\sum {\left(x-\mu \right)}²}{N}, $$

  • 其中μ是平均值, N 是数据集中值的数量。

  • 标准差:这个指标是通过取方差的平方根来计算的。方差与数据的单位不同,因为它是差值的平方;因此,对方差求平方根会使其达到与数据相同的单位。例如,在关于以厘米为单位的平均降雨量的数据集中,方差将给出以 cm 2 为单位的值,这将是不可解释的,而以 cm 为单位的标准差将给出以厘米为单位的平均降雨量偏差的概念。

形状的度量

偏斜度:这度量了一个分布的不对称程度,如图 9-7 所示。

img/498042_1_En_9_Fig7_HTML.jpg

图 9-7

具有不同偏斜度的分布

从图 9-7 中我们可以观察到以下情况:

在正偏态分布中,平均值>中位数

在负偏态分布中,平均值

在完全对称的分布中,均值=中位数=众数

峰度

峰度是对给定的数据分布是曲线、尖峰还是平坦的一种度量。

中层顶分布有一条钟形曲线。薄峰分布是一种有明显峰值的分布。顾名思义,平顶分布有一条平坦的曲线。这些分布如图 9-8 所示。

img/498042_1_En_9_Fig8_HTML.jpg

图 9-8

峰度的表示

求解示例:

某小学 3-7 岁儿童体重(公斤)如下:19,23,19,18,25,16,17,19,15,23,21,23,21,11,6。让我们来计算集中趋势、离差、偏斜度和峰度。

创建 Pandas 系列对象:

代码:

import pandas as pd
a=pd.Series([19,23,19,18,25,16,17,19,15,23,21,23,21,11,6])

Pandas describe 方法既可以用于 series 对象,也可以用于 DataFrame 对象,是一种在一行代码中获得大多数集中趋势度量的便捷方法。平均值为 18.4 千克,第一个四分位数(Q1 或第 25 个百分位数)为 16.5 千克,中位数(第 50 个百分位数)为 19 千克,第三个四分位数(第 75 个百分位数)为 22 千克。

代码:

a.describe()

输出:

count    15.000000
mean     18.400000
std       4.997142
min       6.000000
25%      16.500000
50%      19.000000
75%      22.000000
max      25.000000
dtype: float64

使用模式方法获得模式:

代码:

a.mode()

输出:

0    19
1    23
dtype: int64

值 19 和 23 是最常出现的值。

获得离散度的测量值

可以使用最大值最小值函数并取这两个值之间的差值来计算范围:

代码:

range_a=max(a)-min(a)
range_a

输出:

19

分别使用 stdvar 方法获得标准差和方差:

代码:

a.std()

输出:

4.99714204034952

代码:

a.var()

输出:

24.97142857142857

通过使用来自 scipy.stats 模块的偏斜度峰度函数,获得偏斜度和峰度的度量值:

代码:

from scipy.stats import skew,kurtosis
stats.kurtosis(a)

输出:

0.6995494033062934

峰度的正值意味着分布是细峰的。

偏斜度:

代码:

stats.skew(a)

输出:

-1.038344732097918

负的偏态值意味着分布是负偏态的,平均值小于中值。

注意事项:

  1. 平均值受到异常值(极值)的影响。每当数据集中有异常值时,最好使用中位数。

  2. 标准差和方差与平均值密切相关。因此,如果存在异常值,标准差和方差可能也不是代表性的度量。

  3. 该模式通常用于离散数据,因为连续数据可能有多个模态值。

抽样

当我们试图了解一个群体时,从群体中的每一个受试者那里收集数据是不实际的。更可行的方法是抽取样本,根据样本数据对总体参数进行估计。样本的特征应该与总体的特征一致。以下是采集样本的主要方法。

概率抽样

概率抽样包括从人群中随机选择受试者。进行概率抽样有四种主要方法:

  1. 简单随机抽样:受试者是随机选择的,没有任何偏好。群体中的每个受试者都有同等的被选中的可能性。

  2. 分层随机抽样:将人群分为互斥(非重叠)的组,然后从每组中随机抽取受试者。例如:如果你正在调查评估一所学校对科目的偏好,你可以将学生分成男女两组,并从每组中随机选择科目。这种方法的优点是它代表了总体中的所有类别或组。

  3. 系统随机抽样:定期选择受试者。例如:从 500 人中抽取 100 人作为样本,首先将 500 除以 100,等于 5。现在,每五个人中选一个作为我们的样本。它更容易执行,但可能无法代表群体中的所有受试者。

  4. 整群抽样:在这里,总体被分成不重叠的群,群之间覆盖整个总体。从这些集群中,随机选择一些。要么选择所选聚类的所有成员(一阶段),要么随机选择所选聚类的成员子集(两阶段)。这种方法的优点是成本更低,实施更方便。

非概率抽样

当由于缺乏现成可用的数据而无法使用概率抽样方法收集样本时,我们使用非概率抽样技术。在非概率抽样中,我们不知道受试者被选中进行研究的概率。

它分为以下几种类型:

  1. 方便取样:在这种方法中选择容易接近或可用的受试者。例如,研究人员可以从他们的工作场所或他们工作的大学选择研究对象。这种方法很容易实现,但可能无法代表总体。

  2. 目的性:根据取样的目的选择受试者。例如,如果正在进行一项调查以评估间歇性禁食的有效性,则需要考虑可以进行这种禁食的人群的年龄组,该调查可能只包括 25-50 岁的人。目的性抽样进一步分为

    • 配额抽样:采取配额的方式是在选择样本时考虑到人口的重要特征。如果人口中有 60%的白种人、20%的西班牙人和 20%的亚洲人,那么您选择的样本应该具有相同的百分比。

    • 滚雪球抽样:在这种方法中,研究人员找出他们认识的符合研究标准的人。然后,这个人把他可能认识的人介绍给其他人,这样,样本群体就通过口口相传而增长。该技术可用于缺乏可见性的人群,例如,对患有未被充分报道的疾病的人群的调查。

中心极限定理

中心极限定理指出,如果我们从总体中选择样本,样本的平均值是正态分布的,平均值为 μ ,标准差为$$ {\sigma}_{\overline{x}}. $$

即使总体分布本身不是正态分布,样本均值的分布也类似于正态分布。随着样本量的增加,样本均值的分布变得更接近正态分布,如图 9-9 所示。

img/498042_1_En_9_Fig9_HTML.jpg

图 9-9

样本均值的分布。随着样本量的增加,样本均值的分布类似于正态分布

样本平均值用作总体平均值的估计值,但这种抽样分布的标准差($$ {\sigma}_{\overline{x}}\Big) $$,与总体标准差,不同。样本标准差与总体标准差的关系如下:

$$ {\sigma}_{\overline{x}}=\frac{\sigma }{\sqrt{n}} $$

$$ where\ \sigma\ is\ the\ population\ standard\ deviation\ and\ n\  is\ the\ sample\ size $$

$$ {\sigma}_{\overline{x}} $$被称为标准误差(样本分布的平均值)。随着样本量( n 增加,标准差趋近于 0,样本均值($$ \overline{x}\Big) $$趋近于总体均值( μ )。

估计值和置信区间

  • 点估计:从样本中提取的单一统计量,用于估计未知总体参数。样本平均值用作总体平均值的点估计。

  • 区间估计:总体参数所在的大范围数值。它表示估计总体参数的误差。

  • 置信区间:总体均值所在的区间。对于从总体中抽取的大小为 n 和均值为$$ \overline{x} $$的随机样本(标准偏差为 σ ,均值为 μ ,总体均值的置信区间由以下等式给出:

    • $$ \overline{x}-\frac{z\sigma}{\sqrt{n}}\le \mu \le \overline{x}+\frac{z\sigma}{\sqrt{n}} $$:已知总体标准差

    • $$ \overline{x}-\frac{zs}{\sqrt{n}}\le \mu \le \overline{x}+\frac{zs}{\sqrt{n}} $$:当总体标准差未知时(此方程中的 s 为样本标准差)

求解示例:置信区间

问题:样本(由 10 名受试者组成)取自某一学生群体。这些学生的平均绩点呈正态分布。总体标准偏差未知。基于以下样本值计算总体平均值(整个学生群体的平均绩点)的 95%置信区间:3.1、2.9、3.2、3.4、3.7、3.9、3.9、2.8、3.4、3.6。

解决方案:

以下代码计算总体平均值的 95%置信区间:

代码:

import numpy as np
import scipy.stats as stats
grades = np.array([3.1,2.9,3.2,3.4,3.7,3.9,3.9,2.8,3.4,3.6])
stats.t.interval(0.95, len(grades)-1, loc=np.mean(grades), scale=stats.sem(grades))

输出:

(3.1110006165952773, 3.668999383404722)

解释:学生群体的平均绩点有 95%的可能性落在 3.11 和 3.67 之间。

前面代码的解释:我们首先为样本观测值定义一个 NumPy 数组,然后调用 stats.t.interval 函数。我们向该函数传递以下参数:置信区间的值(0.05)、自由度(观察总数:1)、样本均值和标准误差(由函数 stats.sem 计算)。该函数返回两个值–置信区间下限(LCI)和置信区间上限(UCI)。请注意,使用 stats.t.interval 函数是因为总体标准偏差未知。如果它是已知的,我们将使用函数 stats.norm.interval.

抽样误差的类型

如果我们抽取一个样本,并根据这个样本对整个总体进行推断,那么不可避免地会出现误差。这些错误可以大致分类如下:

  • 抽样误差:总体样本估计值与实际总体估计值之间的差异

  • 覆盖误差:当人口没有被充分代表,一些群体被排除在外时发生

  • 无应答错误:当我们未能纳入满足研究标准的无应答受试者,但由于他们没有回答调查问题而被排除在外时发生

  • 测量误差:由于测量方法或工具的缺陷,没有测量出正确的参数

我们现在继续假设检验的概念。

假设检验

假设是给出未知变量或参数估计值的陈述。如果我们试图从这个人口中抽取一个样本来找出一个城市中的人的平均年龄,并且我们发现这个样本中的人的平均年龄是 34 岁,我们的假设陈述可以如下:“这个城市中的人的平均年龄是 34 岁。”

假设检验的基本概念

在假设检验中,我们构建两个陈述,称为无效假设和交替假设。

  • 零假设:用术语 H 0 表示,这是需要检验的假设。它基于现状不变的原则。如果样本均值为 70,而历史总体均值为 90,则零假设将表明总体均值等于 90。

  • 备选假设:用术语H1 表示,如果原假设不成立,那么这个假设就是人们所相信的。另一个假设(使用前面的例子)是平均值大于、小于或不等于 90。

我们要么拒绝零假设,要么不能拒绝零假设。请注意,拒绝零假设并不意味着替代假设是正确的。假设检验的结果只是暗示或表明关于总体的一些情况,并不能最终证明或否定任何假设。

假设检验中使用的关键术语

让我们看看假设检验中的一些常用术语:

第一类错误,或显著性的 l 水平,用符号 α 表示,是当其为真时拒绝零假设的错误。它也可以定义为总体参数位于其置信区间之外的概率。如果置信区间为 95%,显著性水平为 0.05,或者总体参数有 5%的可能性不在根据样本计算的置信区间内。

第一类错误的例子:X 先生出了皮疹,他去医生那里做水痘测试。假设他没有这种疾病。医生基于一些错误的测试错误地做出了水痘的诊断,但事实是 X 先生并没有这种疾病。这是一个当零假设为真时拒绝零假设的典型例子,这就是 1 型错误。

第二类误差,用符号 β 表示,是零假设为假时不被拒绝时出现的误差。在前面的水痘例子中,如果 X 先生患有水痘,但医生没有诊断它,医生正在犯 2 型错误。

单样本检验:这是一种在考虑只有一个总体时使用的检验,取一个样本来观察从样本和总体参数计算的值之间是否有差异。

双样本检验:这是一种从两个不同人群中抽取样本的检验。这有助于根据样本参数评估总体参数是否不同。

关键检验统计量:决定是否拒绝零假设的样本检验统计量的极限值。在图 9-10 中,z=1.96 和 z=-1.96 为临界值。大于 1.96 且小于-1.96 的 z 值导致无效假设被拒绝。

拒绝区域:拒绝零假设的取值范围。接受区域是与零假设成立的极限相对应的区域。

拒绝和接受的区域如图 9-10 所示。

img/498042_1_En_9_Fig10_HTML.jpg

图 9-10

接受和拒绝的区域

双尾检验:拒绝区域位于分布的两个尾部。

双尾检验的例子:从一个有 50 名学生的班级中抽取 10 名学生的样本,以查看该班级的平均分数相对于其历史平均分数是否有变化。这是一个我们将进行双尾检验的例子,因为我们只是在检验均值的变化,我们不知道这种变化是正的还是负的。

单尾测试:拒绝区域位于右尾(上单尾)或左尾(下单尾),但不在两条尾巴上。

一个 50 人的班级正在进行课外辅导以提高成绩。这些特殊的班级被认为提高了分数。为了检验这一假设,我们使用总体样本进行了单尾检验(上图),因为我们在检验平均分数是否增加了。拒绝区域将位于右尾。

低尾测试的例子:由于政治动荡,学生旷课的情况有所增加。据信,这些事件可能会对学生的成绩产生负面影响。为了检验这一假设,我们使用总体样本进行了单尾检验(下方),因为我们在检验平均分数是否降低了。排斥区位于左尾巴。

p 值 ( 用字母 p ) 表示假设零假设为真,获得一个至少与观测值一样极端的检验统计值的概率。p 值通常用于假设检验,以决定是否拒绝无效假设。p 值通常与 0.05 的显著性水平进行比较。

如果 p < 0.05 ,这将意味着样本数据是随机的且不代表总体的概率非常低。在这种情况下,我们拒绝零假设。

如果 p > 0.05 ,则该样本不代表总体的可能性较大。在这种情况下,我们不能拒绝零假设。

假设检验的步骤

  1. 陈述无效假设和替代假设

  2. 确定显著性水平,获得检验统计量的临界值

  3. 选择适当的测试:

    Choose the test based on the following parameters:

    • 样本数目

    • 人口是否正态分布

    • 正在测试的统计数据

    • 样本量

    • 总体标准差是否已知

  4. 获取相关的检验统计量(z 统计量/t 统计量/卡方统计量/f 统计量)或 p 值

  5. 将关键测试统计数据与计算的测试静态数据进行比较,或将 p 值与 0.05 进行比较

    Reject the null hypothesis based on either the test statistic or the p-value:

    • Using the test statistic:

      • 计算测试静态>临界测试统计(上尾测试)

      • 计算的测试静态

      运筹学

    • 如果 p<0.05,则使用 p 值(p)

  6. 根据前面的比较得出一个推论

单样本 z 检验

当我们想要验证总体均值是否不同于其历史值或假设值时,使用该检验。

单样本 z 检验的标准:

  • 从中抽取样本的总体是正态分布的

  • 样本量大于 30

  • 抽取一个样本

  • 我们正在测试总体平均值

  • 总体标准差是已知的

测试统计量的计算公式:$$ z=\frac{\left(\overline{x}-\mu \right)}{\sigma /\sqrt{n}} $$

其中$$ \overline{x} $$为样本均值, μ 为总体均值, σ 为总体标准差, n 为样本量

求解示例:单样本 z 检验

问题:当地一家意大利餐厅的平均配送时间为 45 分钟,标准差为 5 分钟。餐厅收到了一些顾客的投诉,并决定分析最近的 40 份订单。这 40 份订单的平均交货时间为 48 分钟。在 5%的显著性水平上进行适当的测试,以确定交付时间是否增加了。

回答:

  1. 陈述假设:

    μ 为餐厅的平均送货时间(总体平均值)

    零假设: H 0 : μ =45

    备选假设:H1:μ>45

  2. 确定显著性水平: α =0.05

  3. Select the appropriate hypothesis test:

    • 样本数量:1

    • 样本量: n =40(大)

    • 我们要检验的是:样本均值($$ \overline{x}= $$ 48)和总体均值( μ =45)之间是否存在差异

    • 已知总体标准差( σ =5)

    我们根据前面的数据选择单样本 z 检验。

  4. Obtain the test statistic and p-value, with the help of the following equation:

    $$ z=\frac{\left(\overline{x}-\mu \right)}{\sigma /\sqrt{n}} $$

    Substituting the values $$ \overline{x}= $$48, μ=45, σ = 5, and n=40:

    z=3.7947
    
    

    使用 stats.norm.cdf 函数计算与此 z 值对应的 p 值:

    CODE:

    import scipy.stats as stats
    stats.norm.cdf(z)
    
    

    Output:

    0.999
    
    
  5. 比较 p 值和显著性水平(0.05):

    由于计算的 p 值是> α ,我们无法拒绝零假设。

  6. 推论:

    在 0.05 的水平上,样本的平均交付时间和历史总体平均值之间没有显著差异。

双样本样本 z 检验

双样本 z 检验与单样本 z 检验相似,唯一的区别如下:

  • 考虑两个群体/人群,我们从每个人群中抽取一个样本

  • 两种总体分布都是正态分布

  • 两个总体标准偏差都是已知的

  • 计算检验统计量的公式:$$ :z=\frac{\left({\overline{x}}_1-{\overline{x}}_2\right)}{\sqrt{\left[\frac{\sigma_1²}{n_1}+\frac{\sigma_2²}{n_2}\right]}} $$

求解示例:双样本样本 z 检验

某组织在 A 和 b 两个生产单元生产 LED 灯泡,质量控制团队认为 A 单元的生产质量优于 b 单元,质量是通过灯泡工作的时间来衡量的。该小组从两个单位提取样本进行测试。单元 A 和单元 B 的 LED 灯泡平均寿命分别为 1001.3 和 810.47。样品尺寸为 40 和 44。已知总体方差:$$ {\sigma}_A² $$ =48127 和$$ {\sigma}_B² $$ =59173。

在 5%的显著性水平上进行适当的测试,以验证质量控制团队的声明。

解决方案:

  1. 陈述假设:

    设 A、B 单元的 LED 灯泡平均寿命分别为 μ A 和μ B

    零假设:H0:μAμB

    候补假设:H1:μA>μB

    这是一个单尾(上尾)测试

  2. 确定显著性水平: α =0.05

  3. 选择适当的假设检验:

    • 样本数量:2 个样本(从两个不同的人群中抽取样本)

    • 样本量:大( n A = 40,以及 n B = 44)

    • 我们测试的内容:比较单元 A 和单元 B 中 LED 灯泡的平均寿命

    • 人口特征:人口的分布是未知的,但人口方差是已知的

      因此,我们进行双样本 z 检验。

  4. 计算检验统计量和 p 值

    Use the following equation:

    $$ z=\frac{\left({\overline{x}}_1-{\overline{x}}_2\right)}{\sqrt{\left[\frac{\sigma_1²}{n_1}+\frac{\sigma_2²}{n_2}\right]}} $$

    将值$$ {\overline{x}}_1=1001.3{\overline{,x}}_2 $$ =810.47, n 1 = 40, n 2 = 44 以及方差(sigma)值 48127 和 59173 代入上式计算 z :

    CODE:

    z=(1001.34-810.47)/(48127/40+59173/44)**0.5
    
    

    Output:

    3.781260568723408
    
    

    使用 stats.norm.cdf 函数计算与此 z 值对应的 p 值:

    CODE:

    import scipy.stats as stats
    p=1-stats.norm.cdf(z)
    p
    
    

    Output:

    7.801812433294586e-05
    
    

    说明:由于这是一个上尾检验,我们需要计算右尾中值的面积/比例。因此,我们从 1 中减去计算的面积( stats.norm.cdf )。

  5. 将计算的 p 值与显著性水平进行比较:

    由于计算出的 p 值(0.000078) < α (0.05),我们拒绝零假设。

  6. 推断:在单位 A 生产的 LED 灯泡比在单位 B 生产的 LED 灯泡有明显更长的寿命,在 5%的水平。

比例假设检验

比例检验用于名义数据,对于比较百分比或比例很有用。例如,从组织中的一个部门收集反馈的调查可能声称组织中 85%的人对其政策感到满意。历史上满意率一直是 82%。这里,我们将样本中的百分比或比例与总体中的百分比/比例进行比较。以下是比例抽样分布的一些特征:

  • 从样本中取出的比例的抽样分布是近似正态的

  • 本次抽样分布的均值($$ \overline{p} $$ ) =人口比例( p )

  • Calculating the test statistic: The following equation gives the z-value

    $$ z=\frac{\left(\overline{p}-p\right)}{\sqrt{\frac{p\left(1-p\right)}{n}}} $$

其中$$ \overline{p} $$为样本比例, p 为总体比例, n 为样本量。

求解示例:单样本比例 z 检验

这里,我们使用一个已解决的示例来理解单样本比例 z 检验。

问:众所周知,40%的客户对移动服务中心提供的服务感到满意。该中心的客户服务部决定进行一项调查,以评估目前的客户满意率。它调查了 100 个客户,发现 100 个客户中只有 30 个对它的服务满意。在 5%的显著性水平上进行假设检验,以确定满意客户的百分比是否比最初的满意度(40%)有所下降。

解决方案:

  1. 陈述无效假设和替代假设

    设平均顾客满意率为 p

    HT3o:p= 0 4

    H1:p0 4

  2. 确定显著性水平: α =0.05

  3. 选择适当的测试:

    We choose the one-sample z-test for proportions since

    • 样本量很大(100)

    • 取一个样本

    • 我们正在测试人口比例的变化

  4. Obtain the relevant test statistic and p-value

    $$ z=\frac{\left(\overline{p}-p\right)}{\sqrt{\frac{p\left(1-p\right)}{n}}} $$

    其中$$ \overline{p}=0.3,p=0.4,n=100 $$

    计算 z 和 p:

    CODE:

    import scipy.stats as stats
    
    z=(0.3-0.4)/((0.4)*(1-0.4)/100)**0.5
    p=stats.norm.cdf(z)
    p
    
    

    Output:

    0.02061341666858179
    
    
  5. 决定是否拒绝零假设

    p 值(0.02)<0.05 →我们拒绝零假设

  6. 推论:在 5%的显著性水平上,对服务中心的服务满意的顾客百分比降低了

总体比例的双样本 z 检验

这里,我们比较来自两个不同人群的两个独立样本的比例。下面的等式给出了临界测试统计量的公式:

$$ z=\frac{\left(\overline{p_1}-\overline{p_2}\right)}{\sqrt{\frac{p_c\left(1-{p}_c\right)}{N_1}+\frac{p_c\left(1-{p}_c\right)}{N_2}}} $$

在上式中,$$ \overline{p_1} $$是来自第一个样本的比例,$$ \overline{p_2} $$是来自第二个样本的比例。 N 1 为第一个样本的样本量, N 2 为第二个样本的样本量。

p c 为合并方差。

$$ \overline{p_1} $$=$$ \frac{x_1}{N_1} $$$$ \overline{\ {p}_2} $$=$$ \frac{x_2}{N_2} $$$$ {p}_c=\frac{x_1+{x}_2}{N_1+{N}_2} $$

在前面的公式中, x 1 是第一个样本的成功次数, x 2 是第二个样本的成功次数。

让我们借助一个例子来理解两样本比例检验。

问题:一家拼车公司正在调查其司机的投诉,即一些乘客(带着孩子旅行)不符合儿童安全指南(例如,没有带儿童座椅或没有使用安全带)。该公司在两个主要城市进行调查。调查是独立收集的,每个城市抽取一个样本。从收集的数据来看,城市 B 中的乘客似乎比城市 a 中的乘客更不合规。执法机构想知道这两个城市符合儿童安全指南的乘客比例是否不同。下表给出了这两个城市的数据:

|   |

A 城

|

城市 B

|
| --- | --- | --- |
| 调查总数 | Two hundred | Two hundred and thirty |
| 符合标准的人数 | One hundred and ten | One hundred and six |

在 5%的显著性水平上进行适当的测试,以检验假设。

解决方案:

  1. 陈述假设:

    p A 为 A 市符合规范的人数比例 p B 为 B 市符合标准的人数比例。

    零假设:H0:pA=pB

    候补假设:H1:pA!=p??B

    这将是一个双尾检验,因为排斥区可能位于两侧。

  2. 选择适当的假设检验:

    • 样本数量:2(从两个不同的城市取样)

    • 样本量:大( N 1 = 200, N 2 = 230)

    • 我们测试的是:两个城市符合儿童安全指南的乘客比例是否不同

    • 人口特征:人口分布未知;人口差异是未知的。由于样本量很大,我们选择了双样本 z 检验的比例。

  3. 确定显著性水平: α =0.05

  4. 计算检验统计量和 p 值

    Using the following equation:

    $$ z=\frac{\left(\overline{p_1}-\overline{p_2}\right)}{\sqrt{\frac{p_c\left(1-{p}_c\right)}{N_1}+\frac{p_c\left(1-{p}_c\right)}{N_2}}} $$

    使用 stats.norm.cdf 函数计算与此 z 值对应的 p 值:

    CODE:

    x1,n1,x2,n2=110,200,106,230
    p1=x1/n1
    p2=x2/n2
    pc=(x1+x2)/(n1+n2)
    z=(p1-p2)/(((pc*(1-pc)/n1)+(pc*(1-pc)/n2))**0.5)
    p=2*(1-stats.norm.cdf(z))
    p
    
    

    Output:

    0.06521749465064053
    
    
  5. 将 p 值与显著性水平进行比较:

    由于计算出的 p 值(0.065)> α (0.05),我们未能拒绝零假设。

  6. 推断:在 5%的显著性水平上,这些城市遵守儿童安全规范的乘客比例没有显著差异。

t 分布

可能会出现总体标准差未知、样本量较小的情况。在这种情况下,我们使用 t 分布。这种分布也称为学生的 T 分布。“学生”一词在这里并不具有其字面意义。威廉·希利·戈塞在 1908 年首次发表了这个分布,他使用的笔名是“学生”,因此这个分布被广泛称为学生 T 分布。

以下是 t 分布的主要特征:

  • t 分布在形状上类似于正态分布,只是稍微平坦一些。

  • 样本量较小,一般小于 30。

  • t 分布使用自由度的概念。自由度是统计测试中可以独立估计的观察值的数量。让我们用下面的例子来理解自由度的概念:

    假设我们有三个数字:a、b 和 c。我们不知道它们的值,但我们知道这三个数字的平均值,即 5。根据这个平均值,我们计算三个数字的和–15(平均值值的数量,53)。

    我们能给这三个未知数赋值吗?没有;这三个数字中只有两个可以独立分配。假设我们随机地给 a 赋值 4,给 b 赋值 5。现在,c 只能是 6,因为总和必须是 15。因此,即使我们有三个数字,只有两个是自由变化的。

  • 随着样本量的减少,自由度减少,换句话说,从样本参数预测总体参数的确定性降低。

    t 分布中的自由度( df )是样本数( n ) -1,或者换句话说, df = n - 1

单样本 t 检验中的关键检验统计量的公式由以下等式给出:

$$ \mathbf{t}=\frac{\overline{x}-\mu }{s/\sqrt{n}} $$

其中$$ \overline{\ x} $$是样本均值, μ 是总体均值, s 是样本标准差, n 是样本量。

单一样本 t 检验

单样本 t 检验与单样本 z 检验相似,但有以下区别:

  1. 样本量较小(< 30)。

  2. 总体标准差未知;我们使用样本标准偏差来计算标准误差。

  3. The critical statistic here is the t-statistic, given by the following formula:

    $$ t=\frac{\left(\overline{x}-\mu \right)}{s/\sqrt{n}} $$

双样本 t 检验

当我们从两个总体中抽取样本时,使用双样本 t 检验,这两个总体的样本量都小于 30,并且两个总体的标准差都是未知的。

公式:

$$ t=\frac{{\overline{x}}_1-{\overline{x}}_2}{\sqrt{S_p²\left(\frac{1}{n_1}+\frac{1}{n_2}\right)}} $$

其中$$ {\overline{x}}_1\  and\ {\overline{x}}_2 $$是样本均值

自由度:df =n1+n22

汇总方差:$$ {S}_p²=\frac{\left({n}_{1^{-1}}\right){S}_1²+\left({n}_2-1\right){S}_2²}{n_{\dot{1+}}{n}_{\dot{2}}-2} $$

成对样本的双样本 t 检验

该检验用于比较相互依赖的样本的总体均值,也就是说,使用同一个检验组对样本值进行两次测量。

该等式给出了配对双样本 t 检验的检验统计量的临界值:

$$ t=\frac{\overline{d}}{s/\sqrt{n}} $$

其中$$ \overline{d} $$是两个样本元素之差的平均值。两个样本大小相同, n

S =两个样本元素之差的标准差=

$$ \sqrt{\frac{\sum {d}²-{\left(\sum d\right)}²/n}{n-1}} $$

已解决的示例:使用 Scipy 函数进行 t 测试

Scipy 库为 t-test 提供了各种功能。在下面的例子中,我们来看看单样本 t 检验、双样本 t 检验和配对 t 检验的函数。

  1. 用 Scipy 进行单样本 t 检验:

    问题:一家培训机构有 200 名学生,他们正在为学生准备考试,而学生在模拟考试中的平均成绩是 80 分。它抽取了 9 名学生的样本,并记录了他们的分数;好像现在平均分提高了。这是这十个学生的分数:80,87,80,75,79,78,89,84,88。

    在 5%的显著性水平上进行假设检验,以验证平均分是否有显著提高。

    解决方案:

    我们使用单样本 t 检验,因为样本量很小,总体标准偏差未知。让我们阐明无效假设和替代假设。

    H 0 : μ = 80

    h:>80

    *首先,用样本观察值创建一个 NumPy 数组:

    代码:

    a=np.array([80,87,80,75,79,78,89,84,88])
    
    

    现在,调用 stats.ttest_1samp 函数,并传递这个数组和总体平均值。该函数返回 t 统计量和 p 值。

    CODE:

    stats.ttest_1samp(a,80)
    
    

    Output:

    Ttest_1sampResult(statistic=1.348399724926488,  pvalue=0.21445866072113726)
    
    

    决策:由于 p 值大于 0.05,我们无法拒绝零假设。因此,我们不能断定学生的平均分发生了变化。*
    ** 双样本 t 检验(独立样本):

    问:一个教练学院在两个不同的城市有中心。它从每个中心抽取 10 名学生作为样本,记录他们的分数,如下所示:

    中心 A: 80,87,80,75,79,78,89,84,88

    中心 B: 81,74,70,73,76,73,81,82,84

    在 5%的显著性水平上进行假设检验,并验证这两个中心的学生的平均分是否有显著差异。

    解决方案:

    我们使用双样本 t 检验,因为我们从两个独立的组中抽取样本。样本量很小,总体的标准偏差未知。设这些中心每个学生的平均分分别为 μ 1 和μ 2 。无效假设和替代假设如下:

    h【0】:

    h:!=【2】**

    **为每个样本创建 NumPy 数组:

    CODE:

    a=np.array([80,87,80,75,79,78,89,84,88])
    b=np.array([81,74,70,73,76,73,81,82,84])
    
    

    调用 stats.ttest_ind 函数进行双样本 t 检验,并将这些数组作为参数传递:

    CODE:

    stats.ttest_ind(a,b)
    
    

    Output:

    Ttest_indResult(statistic=2.1892354788555664, pvalue=0.04374951024120649)
    
    

    推论:我们可以得出结论,由于 p 值小于 0.05,教练学院两个中心的学生平均分存在显著差异。******* 配对样本的 T 检验:

    问题:教练学院正在开展一项特别计划来提高学生的表现。同一套学生的成绩在专项前后进行对比。在 5%的显著性水平上进行假设检验,以验证分数是否因为该计划而有所提高。

    解决方案:

    CODE:

    a=np.array([80,87,80,75,79,78,89,84,88])
    b=np.array([81,89,83,81,79,82,90,82,90])
    
    

    调用 stats.ttest_rel 函数进行双样本 t 检验,并将这些数组作为参数传递:

    代码:

    stats.ttest_rel(a,b)
    
    

    Output:

    Ttest_relResult(statistic=-2.4473735525455615, pvalue=0.040100656419513776)
    
    

    ***

**我们可以得出结论,在 5%的显著性水平上,由于 p 值小于 0.05,在实施特殊计划后,平均分有所提高。

方差分析

方差分析是一种用于比较两个以上群体平均值的方法。到目前为止,我们只考虑了单个群体或最多两个群体。方差分析中使用的统计分布是 f 分布,其特征如下:

img/498042_1_En_9_Fig11_HTML.jpg

图 9-11

f 分布的形状

  1. 如图 9-11 所示,f 分布有一个单独的尾部(向右),并且只包含正值。

  2. The F-statistic, which is the critical statistic in ANOVA, is the ratio of variation between the sample means to the variation within the sample. The formula is as follows.

    $$ \mathrm{F}=\frac{variation\ between\ sample\ means}{\left( variation\ within\ the\ sample s\right)} $$

  3. 不同的人群被称为治疗。

  4. 较高的 F 统计值意味着与样本内的差异相比,样本间的差异相当大。换句话说,从中抽取样本的人群或处理实际上是互不相同的。

  5. 当样本内的差异相当大时,处理之间的随机差异更有可能发生。

求解示例:方差分析

问题:

一些农业研究科学家已经种植了一种叫做“AB 棉”的新品种棉花。他们使用了三种不同的肥料——A、B 和 C——用于这一品种的三块独立的土地。研究人员想弄清楚产量是否随着所用肥料的种类而变化。下表列出了每英亩蒲式耳的产量。在 5%的显著性水平上进行 ANOVA 测试,看看研究人员是否可以得出产量存在差异的结论。

|

肥料 A

|

肥料 B

|

肥料 C

|
| --- | --- | --- |
| Forty | Forty-five | Fifty-five |
| Thirty | Thirty-five | Forty |
| Thirty-five | Fifty-five | Thirty |
| Forty-five | Twenty-five | Twenty |

解决方案:

  1. 陈述无效假设和替代假设:

    设三个群体的平均产量分别为μ1μ2和μ 3

    零假设:H0:μ1=μ2=μ3

    备选假设:H1:μ1!=μ2!=μ3

  2. 选择适当的测试:

    我们选择 ANOVA 检验是因为我们比较的是三个群体的平均值

  3. 确定显著性水平: α =0.05

  4. 计算临界测试统计值/p 值:

    f_oneway 函数为我们提供方差分析测试的统计量或 p 值。该函数的参数包括三个列表,其中包含每个组的样本值。

    CODE:

    import scipy.stats as stats
    a=[40,30,35,45]
    b=[45,35,55,25]
    c=[55,40,30,20]
    stats.f_oneway(a,b,c)
    
    

    输出:

    F_onewayResult(statistic=0.10144927536231883, pvalue=0.9045455407589628)
    
    
  5. 由于计算的 p 值(0.904)>0.05,我们无法拒绝零假设。

  6. 推论:在 5%的显著性水平上,三种处理之间没有显著差异。

关联的卡方检验

卡方检验是一种非参数检验,用于检验两个变量之间的关联。非参数检验是一种对样本总体的分布不做任何假设的检验。参数检验(包括 z 检验、t 检验、ANOVA)假设样本总体呈正态分布,并对从中抽取样本的总体的分布/形状做出假设。以下是卡方检验的一些特征。

img/498042_1_En_9_Fig12_HTML.jpg

图 9-12

不同自由度的卡方分布

  • 关联卡方检验用于检验一个分类变量的出现频率是否与另一个分类变量的出现频率显著相关。

  • 卡方检验统计量由下式给出:$$ {X}²=\frac{\sum {\left({f}_0-{f}_e\right)}²}{f_e}, $$其中 f 0 表示观察到的频率, f * e * 表示预期频率, X 是检验统计量。使用关联的卡方检验,我们可以评估频率之间的差异是否具有统计学意义。

  • 列联表是变量频率列在不同栏下的表。卡方检验中的自由度公式如下:

    df=(r-1))(c-1)* ,其中 df 为自由度个数, r 为列联表行数, c 为列联表列数。

  • 卡方检验将一组变量的观察值与其期望值进行比较。它确定观察值和期望值之间的差异是否是由于随机因素(如抽样误差),或者这些差异是否具有统计学意义。如果观察值和预期值之间只有很小的差异,这可能是由于采样中的误差。如果两者之间存在实质性差异,则可能表明变量之间存在关联。

  • 图 9-12 显示了不同的 k (自由度)值的卡方分布形状。卡方分布的形状随自由度而变化(在图 9-12 中用 k 表示)。当自由度很少时,它看起来像一个 f 分布。它只有一条尾巴(向右)。随着自由度的增加,它看起来像一条正常的曲线。此外,自由度的增加表明观察值和期望值之间的差异可能是有意义的,而不仅仅是由于采样误差。

求解示例:卡方检验

问:职业咨询服务指导学生,帮助他们了解自己的优势和劣势,以便他们做出适当的职业选择。他们想评估学生的性别和他或她选择的职业之间是否有联系。下表显示了男性和女性的数量,以及职业(由职业 id 如 I001、I002 等给出)。)他们选择追求。

|

职业生涯

|

雄性

|

雌性

|

总计

|
| --- | --- | --- | --- |
| I001 | Forty-one | Seventy-nine | One hundred and twenty |
| I002 | Thirty-two | Twenty-eight | Sixty |
| I003 | Fifty-eight | seventy-eight | One hundred and thirty |
| I004 | Fifty-nine | Thirty-one | Ninety |

回答:

  1. 陈述假设:

    • 零假设: H 0 :性别和职业偏好不相关

    • 替代假设: H 1 :性别和职业偏好相关

  2. Select the appropriate hypothesis test:

    • 变量数量:两个分类变量(性别和职业)

    • 我们在测试什么:职业和性别之间的联系

    我们根据前面的特征进行关联的卡方检验。

  3. 确定显著性水平: α =0.05

  4. 计算检验统计量和 p 值。 chi2_contingency 函数计算检验统计量和 p 值。该函数返回测试统计量、p 值、自由度和预期频率(以数组的形式)。该函数的参数是来自列联表的数组形式的观察值。每个数组代表列联表中的一行。

代码:

import scipy.stats as stats
observations=np.array([[41,79],[32,28],[52,78],[59,31]])
chi2stat,pval,dof,expvalue=stats.chi2_contingency(observations)
print(chi2stat,pval,dof,expvalue)

输出:

23.803453211665776 2.7454871071500803e-05 3 [[55.2 64.8]
 [27.6 32.4]
 [59.8 70.2]
 [41.4 48.6]]

前面输出中突出显示的值是该测试的 p 值。

  1. 将 p 值与显著性水平进行比较:

由于计算出的 p 值(0.000027) < α (0.05),我们拒绝零假设。

  1. 推断:在 5%的显著性水平上,学生的性别和职业选择之间有显著的关联。

使用 p 值时的警告:

假设检验的功效是通过其产生具有统计意义的结果的能力来衡量的,用小于 0.05 的 p 值来表示。在医学和社会科学领域进行的许多研究试验和实验的结果使用 p 值表示。然而,p 值很难解释。它还取决于我们测量的样本大小和偏差大小。具有统计显著性的结果并不能决定性地否定原假设或证明替代假设。置信区间通常比使用 p 值更可取,因为它们更容易解释。

摘要

  1. 组合是指我们可以选择项目的方式的数量,而排列是指我们可以排列它们的方式的数量。

  2. 概率是事件发生的可能性。

    当一个事件的发生概率不影响另一个事件时,两个事件是独立的。独立事件遵循特殊的乘法法则,其中 P(A ∩ B)= P(A)*P(B)。

    互斥事件是那些不能同时发生的事件,这类事件遵循特殊的加法法则,其中 P(AUB)=P(A)+P(B)。

  3. 贝叶斯定理计算事件的后验概率,或者换句话说,在给定一些相关证据的情况下,假设为真的概率。

  4. 随机变量取与实验结果相关的值。有两种类型的随机变量:离散型(只取几个值)和连续型(可以取任意多个值)。

  5. 离散变量可用于二项式分布(单个实验重复多次,每次试验有两种可能的结果)或泊松分布(在给定平均发生率的情况下,对一段时间内发生的次数进行建模)。

  6. The normal distribution is a symmetric bell-shaped curve, using a continuous random variable with most of its values centered around the mean. The standard normal distribution has a mean of 0 and a standard deviation of 1. The formula used in standard normal distributions is as follows:

    $$ z=\frac{\left(x-\mu \right)}{\sigma}\kern0.5em . $$

  7. 连续分布有各种集中趋势(均值、中值、众数)、离散度(范围、方差、标准差)和形状(偏度是不对称的度量,而峰度是分布弯曲度的度量)。

  8. 当无法收集大量人群中所有受试者的数据时,可以使用样本。收集样本的主要方法是概率抽样(从大量人群中随机选择受试者)和非概率抽样(当数据不容易获得时,根据可用性或可获得性进行抽样)。

  9. 假设检验用于根据样本对总体进行推断,但它不能最终确定总体的任何情况。这只是暗示性的。对样本总体进行的两种估计是点估计(使用单个值)和区间估计(使用一系列值)。置信区间是总体均值所在的值的范围,是区间估计的一个例子。

  10. 零假设表示什么都没有改变,而当我们有理由拒绝零假设时,就使用替代假设。当零假设为真时被错误地拒绝时,发生类型 1 错误。相比之下,当我们没有拒绝无效假设时,第二类错误就发生了。

  11. 检验统计量(每个假设检验都不同)或 p 值都可以用来决定是否拒绝零假设。p 值衡量观察到的数据只是偶然出现的可能性。

  12. 当我们检验总体参数是否不等于特定值时,使用双尾检验。相反,当总体参数大于或小于特定值时,使用单尾检验。

单样本检验用于从总体中抽取一个样本,而双样本检验用于比较从不同总体中抽取的样本。
  1. 假设检验可以是参数性的(当我们假设从中抽取样本的总体是正态分布时)或非参数性的(当我们不对总体分布做这样的假设时)。

  2. 参数假设检验可用于比较使用 z 检验(当样本量较大且总体标准差已知时)或 t 检验(小样本量< 30 且总体标准差未知)的平均值。z 检验也可以用来比较比例。当我们需要比较两个以上总体的平均值时,就要使用 ANOVA 检验。卡方检验是一种常用的非参数检验,用于检验变量之间的关联性。

复习练习

问题 1

将 Scipy 函数(右栏)与适当的假设检验(左栏)配对。

|

假设检验

|

Scipy 功能

|
| --- | --- |
| 1.卡方检验 | a.stats.ttest_rel |
| 2.方差分析 | b.stats.ttest_1samp |
| 3.配对 t 检验 | c.stats.f_oneway |
| 4.单样本 t 检验 | d.stats.chi2_contingency |
| 5.双样本(独立)t 检验 | e.stats.ttest_ind |

问题 2

偏斜度衡量的是:

  1. 散布

  2. 集中趋势

  3. 弯曲度

  4. 不对称

问题 3

J 先生接受了一项疫情测试。医生作出临床诊断,J 先生没有这种病。后来,当进行血液测试时,结果呈阳性。医生犯了以下哪些错误?

  1. 类型 0 错误

  2. 类型 1 错误

  3. 类型 2 错误

  4. 未提交任何错误

问题 4

以下哪一项是正确的?

  1. 正常曲线是中间曲线的一个例子

  2. 玩耍的曲线是平的

  3. 薄壳曲线有一个高峰

  4. 上述全部

  5. 以上都不是

问题 5

让我们假设您正在测试电子学习计划在提高学生分数方面的有效性。在引入电子学习计划之前和之后,测量学生的平均分数。使用假设检验比较平均值后,您获得的 p 值为 0.02。这意味着

  1. 零假设为真的概率是 2%。

  2. 您已经明确否定了零假设(即引入电子学习计划前后的平均分数没有差异)。

  3. 有 2%的可能性得到与观察到的结果一样极端或更极端的结果。

  4. 你已经明确证明了另一个假设。

问题 6

一种新的健康饮料声称有 100 卡路里。制造公司通过选择随机独立样本(100 卡路里)进行定期质量控制检查。这种饮料最近的 13 个样本显示了以下卡路里值:78,110,105,72,88,107,85,92,82,92,91,82,103。在 5%的显著性水平上,进行假设检验,看保健饮料的热值是否与最初声称的有所不同。

问题 7

Silver Gym 正在为其客户提供健身兼减肥计划,并声称该计划将在 30 天后导致至少 3 公斤的体重下降。为了验证这一说法,研究了 20 名参加这一项目的客户。他们的体重在进行这个项目前后进行了比较。

20 位客户在健身计划前后的体重如下:

  • before_weights=[56,95,78,67,59,81,60,56,70,78,84,71,90,101,54,60]

  • after_weights=[52,91,77,65,54,78,54,55,65,76,82,66,88,94,53,55]

进行适当的测试,以检验体重减轻 3 公斤的假设(假设人群的体重呈正态分布)。

答案

问题 1

一维;2-c;3-a;4-b;5-e

问题 2

选项 4:不对称(偏斜度是不对称的一种度量)

问题 3

选项 3:类型 2 错误

当无效假设不成立时,如果无效假设没有被拒绝,则犯下类型 2 错误。这里,无效假设是病人没有这种疾病。既然验血结果是阳性的,医生应该拒绝无效假设,并对这种疾病做出诊断。

问题 4

选项 4:以上所有选项

问题 5

选项 3

请记住,p 值只是给了我们获得与观察到的结果一样极端或更极端的结果的概率。它不证明或反驳任何假设。

问题 6

  1. 陈述假设:

    设该饮料的平均卡路里值为 μ

    零假设:H0:μ= 100

    备选假设: H 1 : μ != 100

    这是一个双尾检验。

  2. 选择适当的假设检验:

    We select the one-sample t-test based on the following characteristics:

    • 样本数量:一个样本

    • 样本量:小(n=13)

    • 我们测试的是:平均热值

    • 总体特征:总体呈正态分布,总体标准差未知

  3. 确定显著性水平: α =0.05

  4. 计算检验统计量和 p 值:

    代码:

    import numpy as np
    import scipy.stats as stats
    values=np.array([78,110,105,72,88,107,85,92,82,92,91,82,103])
    stats.ttest_1samp(values,100)
    
    

    Output:

    Ttest_1sampResult(statistic=-2.6371941582527527, pvalue=0.02168579243588164)
    
    
  5. 比较:由于计算的 p 值< α ,我们拒绝零假设。

  6. 推断:可以得出结论,在 5%的水平上,样本的热值和总体的热值之间存在显著差异。

问题 7

  1. 陈述假设:

    d为该人群减肥计划前后体重的平均差值

    零假设:H0:μd*❤️

    备选假设:d≥3

    单尾检验,因为在替代假设中有一个大于或等于号*
    *** Select the appropriate hypothesis test:

    • 样本数量:两个样本(同一受试者取两个不同的样本)

    • 样本量:小(20)

    • 我们测试的是:测试体重减轻的平均差异

    • 人口特征:人口分布是正常的,但人口的变化是未知的。

    基于上述特征,并且由于样本彼此相关(考虑到我们正在比较相同客户的权重),我们进行了配对双样本 t 检验。

    * 确定显著性水平: α =0.05

    * 计算 p 值:

    p 值可以使用如下代码所示的 stats.ttest_rel 等式来计算。

    CODE:

    import scipy.stats as stats
    before_weights=[56,95,78,67,59,81,60,56,70,78,84,71,90,101,54,60]
    after_weights=[52,91,77,65,54,78,54,55,65,76,82,66,88,94,53,55]
    stats.ttest_rel(before_weights,after_weights)
    
    

    Output:

    Ttest_relResult(statistic=7.120275558034701, pvalue=3.504936069662947e-06)
    
    

    * 结论/解释

    **

**由于计算出的 p 值< α (0.05),我们拒绝零假设。

可以得出结论,两组在减肥计划前后存在显著差异。

文献学

https://upload.wikimedia.org/wikipedia/commons/5/5b/Binomialverteilung2.png

https://upload.wikimedia.org/wikipedia/commons/thumb/c/c1/Poisson_distribution_PMF.png/1200px-Poisson_distribution_PMF.png

https://upload.wikimedia.org/wikipedia/commons/thumb/f/fb/Normal_distribution_pdf.svg/900px-Normal_distribution_pdf.svg.png

https://commons.wikimedia.org/wiki/File:Standard_deviation_diagram.svg

https://upload.wikimedia.org/wikipedia/commons/d/d8/Normal_distribution_curve_with_lower_tail_shaded.jpg

https://upload.wikimedia.org/wikipedia/commons/thumb/c/cc/Relationship_between_mean_and_median_under_different_skewness.png/1200px

https://commons.wikimedia.org/wiki/File:Kurtosimailak_euskaraz.pdf

https://upload.wikimedia.org/wikipedia/commons/2/2d/Empirical_CLT_-_Figure_-_040711.jpg

https://upload.wikimedia.org/wikipedia/commons/1/10/Region_of_rejections_or_acceptance.png

wikimedia common(https://commons.wikimedia.org/wiki/File:Chi-square_distributionPDF.png****

posted @ 2024-10-01 21:03  绝不原创的飞龙  阅读(2)  评论(0编辑  收藏  举报