本指南探讨了允许你使用 Python 执行数据分析的最佳实践和基础知识。在本指南中,你将学习如何使用 Jupyter notebook 和 Python 库(如 Pandas , Matplotlib 和 Numpy )轻松、透明地探索和分析数据集。
从本质上讲,数据科学 是关于从大量数据中 提取知识 来生成信息。这基本上是使用数学和计算机科学等几门学科完成的,如统计学,概率模型,机器学习,数据存储,计算机编程等。
欲了解更多信息,我们邀请读者查看 这本书 .
在参加本指南之前,有必要从 Python 编程 开始,我们还将参考 概率和统计 的基本概念,以及 关系代数
遵循本指南后,读者应熟悉以下任务:
Matplotlib
和 Seaborn
进行数据可视化;Numpy
处理表;;Pandas
处理数据集。.我们将根据以下计划提出分层内容:
让我们开始。
为了开始用 Python 分析数据,我们需要有一些背景知识,就像所有其它相关主题一样。现在,我们将尝试解释如何在自己的机器上安装 Jupyter。
如果你还没有 Python ,可以使用 这个方案 直接安装 Python ,包括所有必需的库和 Jupyter notebook。
一旦安装了 Python ,你将需要安装 Jupyter notebook ,以及以下库: Pandas
, Matplotlib
, Numpy
和 SciPy
.
在实践中,有两种可能的解决方案来安装 Jupyter notebook 及其必要的库:
1.1. 与 Anaconda 发行版一起完全安装
对于那些从未安装过 Python 的人,建议直接安装 Anaconda 发行版。这是我们将在本节中做的。请注意, Anaconda 是为数据科学开发的 Python 发行版。
Anaconda 会安装我们需要的一切,但它可以会安装太多(我们不会使用的库,等等)。你可以在此处 下载 Anaconda 的改进版本。但是,目的是不会自动安装库,尤其是 Jupyter 。因此,这是有经验的用户的解决方案。
在 这个页面 你将找到正式的安装说明,以防这些说明不再有效。
要在 Windows 或 macOS 上安装 Anaconda ,你必须:
1.2. 单独安装 Jupyter notebook(不含Anaconda)
为避免安装 Anaconda ,你可以在安装 Python 后按照以下说明操作:
pip
。为此,你只需在控制台中键入 pip
。通常, pip 与 Python 同时启动。接下来,连续输入这些代码:!python -m pip install --upgrade pip
!python -m pip install jupyter
通过在控制台中键入以下命令,你可以验证设置是否正常:
!jupyter notebook
现在你可以创建一个新 notebook 了。
pip 命令
。为此,你只需在控制台键入 pip 即可。然后,在控制台中键入以下命令: !pip install scipy
!pip install numpy
!pip install matplotlib
!pip install pandas
作为替代方案,你现在可以测试 Jupyter ,通过 此链接 而无需安装任何内容。
让我们在 Jupyter notebook 中进行第一次实验:
要创建一个新 notebook, 请从主 Jupyter 窗口中单击 New
然后再打开 Python 3
, 如下所示:
创建一个新 notebook
在启动 Jupyter 的目录中,通常会创建一个名为 Untitled.ipynb
的文件。必须在 In [ ]
标签旁边的字段中键入所有Python命令。为此,只需同时键入几条指令即可。甚至可以定义函数。每个单元格中生成的所有变量都可以在 notebook 的所有单元格中访问。完成输入后,按下 Shift+Enter
执行它们。
要对其进行测试,请键入例如 2 + 5
位于窗口中心的空单元格。然后单击此按钮:
Test
在本节中,我们将记住 Python 编程的基础知识。此外,我们不会列出我们需要掌握的所有内容,但我们将模拟整个问题。好吧,我们第一次听说 蒙提霍尔问题/三门问题(Monty Hall problem) 。该游戏涉及一个针对竞争对手(玩家)的主持人。该竞争对手位于三个关闭的门前。其中一个门后面有一辆车,其它门后面都是一只山羊。首先,它必须指明一扇门。然后,主持人必须打开一扇门,既不是候选人选择的门,也不是隐藏汽车的门。然后,竞争对手有权打开门,或坚持他最初的选择,或打开第三扇门。
首先,让我们在 notebook 中设置我们的工作区:
# For displaying graphics in the code sequence,
# and not in a separate window:
%matplotlib inline
# By using the randint function, which generates numbers
# whole in a random way:
from random import randint, seed
# An Enum is a data structure that consists of a
# set of appointed elements. This type of variable can be
# have as value one of these elements.
from enum import Enum
# For displaying graphs:
import matplotlib.pyplot as plt
可以定义我们的函数。我们将创建一个非常简单的函数。它只代表特定策略的游戏的一部分。
class Strategy(Enum) :
CHANGE = 1
KEEP = 2
在这里,我们定义了 枚举(Enum) 的子类,它将包含可能的策略。此代码涉及使用面向对象的编程概念,包括类和继承。
# Uses the system clock to initialize the generator of
# pseudo-random numbers.
seed()
def Hall_game(strategy) :
''' Simulates part of the Monty Hall game. This function simulates the participant's choice of door, the elimination of a bad door by the presenter, and the final choice. It only returns the result of the game, because that we will only need the result to perform our calculations.
Args:
strategy (strategy) : The player's strategy
Returns:
bool: Has the player won?
'''
doors =[0, 1, 2]
good_door = randint(0,2)
# Choice of player
first_choice = randint(0,2)
# We have two doors left
doors.remove(first_choice)
# The presenter eliminates a door
if first_choice == good_door:
doors.remove(doors[randint(0,1) ])
else:
doors =[good_door]
second_choice = 0
# The second choice depends on the strategy
if strategy == Strategy.CHANGE:
second_choice = doors[0]
elif strategy == Strategy.KEEP:
second_choice = first_choice
else:
raise ValueError("Strategy not recognized!")
return second_choice == good_door
Randint
函数返回其两个参数之间的随机整数。例如,randint(0,1) 将返回 0 或 1 。
我们现在将测试我们的函数。多次运行下一行以确保结果是随机的。
strategy = Strategy.CHANGE
Hall_game(strategy)
True
在这种情况下,有必要定义一个将一遍又一遍地启动游戏的函数,并返回每个游戏的结果 list
.
为了对这些结果进行计算,我们将不再将它们保留为布尔变量( True 或 False ),而是根据玩家的胜利(如果他赢了则为 1 ,如果他输了则为 0 )。
def Play(strategy, nb_turns) :
'''Simulates a sequence of game turns. This function returns the results of several games of the Monty Hall game in the form of a list of winnings by the player.
Args:
strategy (strategy) : The player's strategy
nb_turns (int) : Number of revolutions
Returns:
list: List of players' winnings for each game
'''
return [1 if Hall_game(strategy) else 0 for i in range(nb_turns) ]
对于给定数量的游戏(10000),首先需要知道哪种策略对于玩家最有效。我们有一个列表,其中包含最多 1 个玩家赢得的游戏数量。只需要计算此列表中所有项目的总和 sum function
,即可知道 1 的数量。
print("By changing doors, the player has won {} on 10,000 games."
.format(sum(Play(Strategy.CHANGE, 10000) )) )
print("By keeping his initial choice, the player has won {} out of 10,000 games."
.format(sum(Play(Strategy.KEEP, 10000) )) )
By changing doors, the player has won 6737 on 10,000 games.
By keeping his initial choice, the player has won 3441 out of 10,000 games.
3.1. 使用 Numpy 处理数据
本节将重点介绍如何有效地加载,存储和操作数据。它们可以在各种各样的来源中找到,但它们总是可以被视为数字数组。我们将看到一个操纵这些数组的工具: Numpy 。 NumPy(Numerical Python)提供了一个存储和处理数据的接口。Numpy 数组就像 Python 列表,但是 Numpy 可以让事情变得更有效率,,特别是对于更大的数组。
让我们从导入Numpy开始:
import numpy as np
创建 Numpy 数组
与 Python 列表不同, Numpy 数组只能包含一种类型的成员。有几种方法可以在 Numpy 中创建数组:
# Array of integers:
np.array([1, 2, 3])
array([1, 2, 3])
如果初始列表中有不同类型的数据,Numpy 将尝试将它们全部转换为最通用的类型。例如,整数 int
将转换为 float
数字:
np.array([3.1, 4, 5, 6])
array([3.1, 4. , 5. , 6. ])
作为替代方案,也可以手动设置类型:
np.array([1, 2, 3], dtype='float32')
array([1., 2., 3.], dtype=float32)
在许多情况下,通过直接创建它们更有效,特别是对于大型数组。 Numpy 有以下几个函数:
# An array of length 10, filled with integers that are worth 0
np.zeros(10, dtype=int)
# A 3x5 size array filled with floating point numbers of value 1
np.ones((3, 5) , dtype=float)
# A 3x5 table filled with 3.14
np.full((3, 5) , 3.14)
# A table filled with a linear sequence
# starting from 0 and ending from 20, with a step of 2
np.arange(0, 20, 2)
# A table of 5 values, uniformly spaced between 0 and 1
np.linspace(0, 1, 5)
# This one you already know! Try also "randint" and "normal"
#np.random((3, 3) )
# The identity matrix size 3x3
np.eye(3)
array([[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.]])
数组属性,索引和切片
每个 Numpy 数组都有一些通常有用的属性。
np.random.seed(0)
x1 = np.random.randint(10, size=6) # Dimension table 1
print("dimensions number of x1:", x1.ndim)
print("shape of x1:", x1.shape)
print("size of x1:", x1.size)
print("type of x1:", x1.dtype)
dimensions number of x1: 1
shape of x1: (6,)
size of x1: 6
type of x1: int64
为此,我们经常需要访问数组的一个或多个元素。
print(x1)
# To get access to the first element
print(x1[0])
# to get access to the last element
print(x1[-1])
x2 = np.random.randint(10, size=(3, 4) ) # Dimension table of 2
print(x2[0,1])
# We can also set different values
x1[1] = "1000"
print(x1)
x1[1] = 3.14
print(x1)
[5 0 3 3 7 9]
5
9
5
[ 5 1000 3 3 7 9]
[5 3 3 3 7 9]
我们可以与索引元素相同的方式使用 []
,我们通过组合 []
和 :
访问一组元素。语法有一个简单的规则:x[start:end:step]
。
print(x1[:5]) # The first five elements
print(x1[5:]) # Elements starting at index 5
print(x1[::2]) # One of two elements
[5 3 3 3 7]
[9]
[5 3 7]
可以连接两个或多个数组:
x = np.array([1, 2, 3])
y = np.array([3, 2, 1])
np.concatenate([x, y])
array([1, 2, 3, 3, 2, 1])
到目前为止,我们已经看到了 Numpy 数组上非常基本的东西。从这里开始,我们将看到是什么让 Numpy 变得非常重要。 Python 的基准实现(也称为 CPython )非常灵活,但这种灵活性使其无法使用所有可能的优化。
def reverse_calculation(values) :
output = np.empty(len(values) )
for i in range(len(values) ):
output[i] = 1.0 / values[i]
return output
values = np.random.randint(1, 10, size=5)
print(reverse_calculation(values) )
wide_array = np.random.randint(1, 100, size=1000000)
[0.11111111 0.5 0.16666667 0.11111111 0.2 ]
我们现在提供此类操作的总览:
# First of all, there are simple mathematical operations
x = np.arange(4)
print("x =", x)
print("x + 5 =", x + 5)
print("x - 5 =", x - 5)
print("x * 2 =", x * 2)
print("x / 2 =", x / 2)
print("x // 2 =", x // 2)
x = [0 1 2 3]
x + 5 = [5 6 7 8]
x - 5 = [-5 -4 -3 -2]
x * 2 = [0 2 4 6]
x / 2 = [0. 0.5 1. 1.5]
x // 2 = [0 0 1 1]
或者,你也可以在 Numpy 数组上调用函数,甚至在 Python 列表上调用函数。
x = [-6, -3, 2, 5]
print("Absolute value:", np.abs(x) )
print("Exponential:", np.exp(x) )
print("Logarithm:", np.log(np.abs(x) ))
Absolute value: [6 3 2 5]
Exponential: [2.47875218e-03 4.97870684e-02 7.38905610e+00 1.48413159e+02]
Logarithm: [1.79175947 1.09861229 0.69314718 1.60943791]
你还可以在数组上执行布尔运算:
x = np.random.rand(4,4)
x > 0.7
array([[ True, True, False, True],
[ True, False, False, False],
[False, False, False, False],
[False, True, False, False]])
Numpy 有在它的数组上对这些数据的求和函数:
L = np.random.random(10)
np.sum(L)
4.698357490059848
3.2. 使用 Matplotlib 处理数据
创建 Matplotlib 是为了直接从 Python 生成图形。在本节中,我们将重点放在使用 Matplotlib 作为 Jupyter notebook 中的可视化工具。
要开始这个过程,我们设置工作区:
%matplotlib inline
import matplotlib.pyplot as plt
plt.style.use('seaborn-whitegrid')
import numpy as np
让我们首先检查一个简单的案例,绘制一个 sin
函数的曲线:
fig = plt.figure()
ax = plt.axes()
x = np.linspace(0, 10, 1000)
ax.plot(x, np.sin(x) );
output_49_0.png
fig
变量是指包含所有项目(轴,标签,数据等)的容器。轴与上面显示的方形匹配,然后将包含图形中的数据。
在离散数据集(点)的情况下,我们经常使用误差线来表示每个点的不确定性,以确定其准确值:
x = np.linspace(0, 30, 80)
dy = 0.3
y = np.sin(x) + dy * np.random.randn(80)
plt.errorbar(x, y, yerr=dy, fmt='.k') ;
output_51_0.png
Pandas 库是 Python 中数据科学的基本库之一。 Pandas 提供易于使用且功能强大的数据结构以及快速使用它们的方法。在本节中,我们将讨论 Pandas 库感兴趣的内容,以及该库主要对象的基本操作 Dataframe
.
这个 pandas 可以用 numpy 数组表示:
import numpy as np
panda_numpy = np.array([200,50,100,80])
panda_numpy
array([200, 50, 100, 80])
可以这样做:
family = [
np.array([100, 5, 20, 80]) , # mom
np.array([50, 2.5, 10, 40]) , # baby
np.array([110, 6, 22, 80]) , # daddy
]
让我们使用 Pandas 表示 family
:
import pandas as pd
family_df = pd.DataFrame(family)
family_df
可用于表示数组的对象是 DataFrame
对象
实际上,通过指定列名和行名,我们可以做得更好:
family_df = pd.DataFrame(family,
index = ['mom','baby','dad'],
columns = ['legs','hair','hands','belly'])
family_df
<div> <style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
vertical-align: top;
}
.dataframe thead th {
text-align: right;
}
</style> <table border="1" class="dataframe"> <thead> <tr style="text-align: right;"> <th></th> <th>legs</th> <th>hair</th> <th>hands</th> <th>belly</th> </tr> </thead> <tbody> <tr> <th>mom</th> <td>100.0</td> <td>5.0</td> <td>20.0</td> <td>80.0</td> </tr> <tr> <th>baby</th> <td>50.0</td> <td>2.5</td> <td>10.0</td> <td>40.0</td> </tr> <tr> <th>dad</th> <td>110.0</td> <td>6.0</td> <td>22.0</td> <td>80.0</td> </tr> </tbody> </table> </div>
以下是 Dataframes 的一些小功能。首先,访问我们数组的 belly 列。有两种可能的语法,它们返回完全相同的结果:
family_df.belly
family_df["belly"]
mom 80.0
baby 40.0
dad 80.0
Name: belly, dtype: float64
我们现在将逐个看到整个系列,通过 iterrows
返回一个元组作为其第一个元素的元组索引和行的内容:
for ind, content in family_df.iterrows() :
print("Family: %s :" % ind)
print(content)
Family: mom :
legs 100.0
hair 5.0
hands 20.0
belly 80.0
Name: mom, dtype: float64
Family: baby :
legs 50.0
hair 2.5
hands 10.0
belly 40.0
Name: baby, dtype: float64
Family: dad :
legs 110.0
hair 6.0
hands 22.0
belly 80.0
Name: dad, dtype: float64
# Create a CSV file called dataset
%%writefile dataset.csv
John,Doe,120 jefferson st.,Riverside, NJ, 08075
Jack,McGinnis,220 hobo Av.,Phila, PA,09119
"John ""Da Man""",Repici,120 Jefferson St.,Riverside, NJ,08075
Stephen,Tyler,"7452 Terrace ""At the Plaza"" road",SomeTown,SD, 91234
,Blankman,,SomeTown, SD, 00298
"Joan ""the bone"", Anne",Jet,"9th, at Terrace plc",Desert City,CO,00123
Overwriting dataset.csv
Pandas 库是指向数组操作的指针。因此,可以使用 Pandas 读取 CSV 文件:从 CSV 创建数据框只需要一行:
data = pd.read_csv("dataset.csv", sep=";")
data
变量现在包含一个包含 csv 文件数据的数据帧;;
;pd.read_csv
期望以逗号分隔的值data