Python数据处理历史悠久,除了基础的Numpy、Scipy、pandas构建繁荣生态,还有谷歌推出的tensorflow、jax高性能工具
%load_ext autoreload
%autoreload 2
import matplotlib.pyplot as plt
import seaborn
seaborn.set()
plt.rcParams["font.sans-serif"] = ["SimHei"]
import numpy as np
import pandas as pd
from scipy import sparse
from tqdm.notebook import tqdm
首发年份 | 名称 | 场景 |
---|---|---|
1991 | Python | 编程语言 |
2001 | ipython | 增强shell |
2001 | SciPy | 算法库 |
2006 | Numpy | 数组运算 |
2007 | Cython | AOT静态编译 |
2008 | Pandas | 标签数组运算 |
2010 | scikit-learn | 机器学习 |
2012 | ipython notebook | 计算环境 |
2012 | anaconda | 管理工具 |
2012 | Numba | llvm实现JIT编译器 |
2012 | pyspark | 集群运算 |
2015 | jupyter | 多语言支持 |
2015 | TensorFlow | 深度学习 |
2018 | jax | Numpy+autogrd+JIT+GPU+TPU |
from IPython.display import Video
# https://github.com/Sentdex/NNfSiX
# Video("2.data-elt/cat_neural_network.mp4", embed=True)
x1 | x2 | x3 | Y |
---|---|---|---|
0 | 0 | 1 | 0 |
0 | 1 | 1 | 1 |
1 | 0 | 1 | 1 |
1 | 1 | 1 | 0 |
X = np.array([[0, 0, 1], [0, 1, 1], [1, 0, 1], [1, 1, 1]])
y = np.array([[0], [1], [1], [0]])
公式1 $$ \hat y = \sigma(W_2\sigma(W_1x+ b_1) + b_2) $$
公式2(sigmoid) $$ \sigma = \frac {1} {1 + e^{-x}} $$
公式3(sigmoid导数) $$ \sigma' = \sigma(x) \times (1 - \sigma(x)) $$
公式4 $$ Loss(Sum\ of\ Squares\ Error) = \sum_{i=1}^n(y-\hat y)^2 $$
def σ(x):
return 1 / (1 + np.exp(-x))
def σ_dvt(x):
return σ(x) * (1 - σ(x))
class NeuralNetwork(object):
def __init__(self, x, y):
self.x = x
self.y = y
self.w1 = np.random.rand(self.x.shape[1], 4)
self.w2 = np.random.rand(4, 1)
self.yhat = np.zeros(self.y.shape)
def feedforward(self):
self.layer1 = σ(self.x @ self.w1)
self.yhat = σ(self.layer1 @ self.w2)
def backprop(self):
gd = 2 * (self.y - self.yhat) * σ_dvt(self.yhat)
d_w2 = self.layer1.T @ gd
d_w1 = self.x.T @ (gd @ (self.w2.T) * σ_dvt(self.layer1))
self.w1 += d_w1
self.w2 += d_w2
nn = NeuralNetwork(X, y)
train = []
for i in tqdm(range(10000)):
nn.feedforward()
nn.backprop()
loss = sum((_[0] - _[1])[0] ** 2 for _ in zip(nn.y, nn.yhat))
train.append(loss)
print(nn.yhat)
HBox(children=(FloatProgress(value=0.0, max=10000.0), HTML(value='')))
[[0.00644673] [0.9909493 ] [0.99080728] [0.00803459]]
def show_plot(x, y):
plt.figure(figsize=(15, 5))
plt.plot(
x,
y,
linewidth=3,
linestyle=":",
color="blue",
label="Sum of Squares Error",
)
plt.xlabel("训练次数")
plt.ylabel("训练损失")
plt.title("训练损失随次数增加而递减")
plt.legend(loc="upper right")
plt.show()
show_plot(range(len(train)), train)
show_plot(range(4000, len(train)), train[4000:])
NumPy在C语言的基础上开发ndarray
对象,其数据类型也是在C语言基础上进行扩充。
CPython的整型对象是一个PyObject_HEAD是C语言结构体,包含引用计数、类型编码和数据大小等信息,相比C语言的整型增加了很多开销,Numpy进行了优化。
# 创建一个长度为10的数组,数组的值都是0
np.zeros(10, dtype=int)
array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
# 创建一个3x5的浮点型数组,数组的值都是1
np.ones((3, 5), dtype=float)
array([[1., 1., 1., 1., 1.], [1., 1., 1., 1., 1.], [1., 1., 1., 1., 1.]])
# 创建一个3x5的浮点型数组,数组的值都是3.14
np.full((3, 5), 3.14)
array([[3.14, 3.14, 3.14, 3.14, 3.14], [3.14, 3.14, 3.14, 3.14, 3.14], [3.14, 3.14, 3.14, 3.14, 3.14]])
# 创建一个线性序列数组,从0开始,到20结束,步长为2(它和内置的range()函数类似)
np.arange(0, 20, 2)
array([ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18])
# 创建一个5个元素的数组,这5个数均匀地分配到0~1区间
np.linspace(0, 1, 5)
array([0. , 0.25, 0.5 , 0.75, 1. ])
# NumPy的随机数生成器设置一组种子值,以确保每次程序执行时都可以生成同样的随机数组:
np.random.seed(1024)
# 创建一个3x3的、0~1之间均匀分布的随机数组成的数组
np.random.random((3, 3))
array([[0.64769123, 0.99691358, 0.51880326], [0.65811273, 0.59906347, 0.75306733], [0.13624713, 0.00411712, 0.14950888]])
# 创建一个3x3的、均值为0、标准差为1的正态分布的随机数数组
np.random.normal(0, 1, (3, 3))
array([[ 0.7729004 , 1.64294992, -0.12721717], [ 0.91598327, 0.52267255, -0.22634267], [ 1.41873344, -0.16232799, 0.53831355]])
# 创建一个3x3的、[0, 10)区间的随机整型数组
np.random.randint(0, 10, (3, 3))
array([[6, 4, 4], [1, 0, 1], [8, 7, 0]])
# 创建一个3x3的单位矩阵
np.eye(3)
array([[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]])
# 创建一个由3个整型数组成的未初始化的数组,数组的值是内存空间中的任意值
np.empty(3)
array([1., 1., 1.])
NumPy实现一种静态类型、可编译程序接口(ufunc),实现向量化(vectorize)操作,避免for循环,提高效率,节约内存。
通用函数有两种存在形式:
NumPy通用函数的使用方式非常自然,因为它用到了Python原生的算术运算符(加、减、乘、除)、绝对值、三角函数、指数与对数、布尔/位运算符。
运算符 | 对应的通用函数 | 描述 |
---|---|---|
+ |
np.add |
加法运算(即1 + 1 = 2 ) |
- |
np.subtract |
减法运算(即3 - 2 = 1 ) |
- |
np.negative |
负数运算 (即-2 ) |
* |
np.multiply |
乘法运算 (即2 * 3 = 6 ) |
/ |
np.divide |
除法运算 (即3 / 2 = 1.5 ) |
// |
np.floor_divide |
地板除法运算(floor division,即3 // 2 = 1 ) |
** |
np.power |
指数运算 (即2 ** 3 = 8 ) |
% |
np.mod |
模/余数 (即9 % 4 = 1 ) |
np.abs |
绝对值 |
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]
x = [1, 2, 3]
print("x =", x)
print("e^x =", np.exp(x))
print("2^x =", np.exp2(x))
print("3^x =", np.power(3, x))
print("x =", x)
print("ln(x) =", np.log(x))
print("log2(x) =", np.log2(x))
print("log10(x) =", np.log10(x))
x = [1, 2, 3] e^x = [ 2.71828183 7.3890561 20.08553692] 2^x = [2. 4. 8.] 3^x = [ 3 9 27] x = [1, 2, 3] ln(x) = [0. 0.69314718 1.09861229] log2(x) = [0. 1. 1.5849625] log10(x) = [0. 0.30103 0.47712125]
scipy.special
提供了大量统计学函数。例如,Γ函数和β函数
from scipy import special
x = [1, 5, 10]
print("gamma(x) =", special.gamma(x))
print("ln|gamma(x)| =", special.gammaln(x))
print("beta(x, 2) =", special.beta(x, 2))
gamma(x) = [1.0000e+00 2.4000e+01 3.6288e+05] ln|gamma(x)| = [ 0. 3.17805383 12.80182748] beta(x, 2) = [0.5 0.03333333 0.00909091]
x = np.arange(1, 6)
np.add.reduce(x)
15
同样,对multiply
通用函数调用reduce
方法会返回数组中所有元素的乘积:
np.multiply.reduce(x)
120
np.add.accumulate(x)
array([ 1, 3, 6, 10, 15])
np.multiply.accumulate(x)
array([ 1, 2, 6, 24, 120])
NumPy提供了专用的函数(
np.sum
、np.prod
、np.cumsum
、np.cumprod
),它们也可以实现reduce
的功能
任何通用函数都可以用outer
方法获得两个不同输入数组所有元素对的函数运算结果。用一行代码实现一个99乘法表:
x = np.arange(1, 10)
np.multiply.outer(x, x)
array([[ 1, 2, 3, 4, 5, 6, 7, 8, 9], [ 2, 4, 6, 8, 10, 12, 14, 16, 18], [ 3, 6, 9, 12, 15, 18, 21, 24, 27], [ 4, 8, 12, 16, 20, 24, 28, 32, 36], [ 5, 10, 15, 20, 25, 30, 35, 40, 45], [ 6, 12, 18, 24, 30, 36, 42, 48, 54], [ 7, 14, 21, 28, 35, 42, 49, 56, 63], [ 8, 16, 24, 32, 40, 48, 56, 64, 72], [ 9, 18, 27, 36, 45, 54, 63, 72, 81]])
NumPy也可以通过广播实现向量化操作。广播可以用于不同大小数组的二元通用函数(加、减、乘等)的一组规则:
a = np.arange(3)
a + 5
array([5, 6, 7])
np.ones((3, 3)) + a
array([[1., 2., 3.], [1., 2., 3.], [1., 2., 3.]])
根据规则1,数组a
的维度数更小,所以在其左边补1:
b.shape -> (3, 3)
a.shape -> (1, 3)
根据规则2,第一个维度不匹配,因此扩展这个维度以匹配数组:
b.shape -> (3, 3)
a.shape -> (3, 3)
现在两个数组的形状匹配了,可以看到它们的最终形状都为(3, 3)
:
b = np.arange(3)[:, np.newaxis]
b + a
array([[0, 1, 2], [1, 2, 3], [2, 3, 4]])
根据规则1,数组a
的维度数更小,所以在其左边补1:
b.shape -> (3, 1)
a.shape -> (1, 3)
根据规则2,两个维度都不匹配,因此扩展这个维度以匹配数组:
b.shape -> (3, 3)
a.shape -> (3, 3)
现在两个数组的形状匹配了,可以看到它们的最终形状都为(3, 3)
:
SN = np.random.poisson(0.2, (10, 10)) * np.random.randint(0, 10, (10, 10))
SN
array([[ 8, 0, 0, 0, 9, 0, 0, 0, 0, 3], [ 0, 0, 9, 0, 7, 0, 0, 0, 0, 4], [ 0, 0, 0, 0, 0, 0, 0, 0, 8, 0], [ 0, 0, 0, 9, 0, 0, 0, 4, 0, 0], [ 0, 5, 0, 0, 0, 7, 0, 0, 0, 0], [ 0, 8, 8, 1, 0, 1, 4, 0, 0, 0], [ 0, 18, 0, 0, 0, 0, 0, 0, 0, 4], [ 0, 0, 4, 0, 0, 0, 5, 0, 0, 0], [ 0, 0, 0, 0, 5, 0, 0, 0, 0, 0], [ 0, 0, 0, 9, 0, 0, 4, 0, 0, 0]])
rows, cols = np.nonzero(SN)
vals = SN[rows, cols]
rows, cols, vals
(array([0, 0, 0, 1, 1, 1, 2, 3, 3, 4, 4, 5, 5, 5, 5, 5, 6, 6, 7, 7, 8, 9, 9]), array([0, 4, 9, 2, 4, 9, 8, 3, 7, 1, 5, 1, 2, 3, 5, 6, 1, 9, 2, 6, 4, 3, 6]), array([ 8, 9, 3, 9, 7, 4, 8, 9, 4, 5, 7, 8, 8, 1, 1, 4, 18, 4, 4, 5, 5, 9, 4]))
X = sparse.coo_matrix(SN)
X
<10x10 sparse matrix of type '<class 'numpy.int64'>' with 23 stored elements in COOrdinate format>
print(X)
(0, 0) 8 (0, 4) 9 (0, 9) 3 (1, 2) 9 (1, 4) 7 (1, 9) 4 (2, 8) 8 (3, 3) 9 (3, 7) 4 (4, 1) 5 (4, 5) 7 (5, 1) 8 (5, 2) 8 (5, 3) 1 (5, 5) 1 (5, 6) 4 (6, 1) 18 (6, 9) 4 (7, 2) 4 (7, 6) 5 (8, 4) 5 (9, 3) 9 (9, 6) 4
X2 = sparse.coo_matrix((vals, (rows, cols)))
X2.todense()
matrix([[ 8, 0, 0, 0, 9, 0, 0, 0, 0, 3], [ 0, 0, 9, 0, 7, 0, 0, 0, 0, 4], [ 0, 0, 0, 0, 0, 0, 0, 0, 8, 0], [ 0, 0, 0, 9, 0, 0, 0, 4, 0, 0], [ 0, 5, 0, 0, 0, 7, 0, 0, 0, 0], [ 0, 8, 8, 1, 0, 1, 4, 0, 0, 0], [ 0, 18, 0, 0, 0, 0, 0, 0, 0, 4], [ 0, 0, 4, 0, 0, 0, 5, 0, 0, 0], [ 0, 0, 0, 0, 5, 0, 0, 0, 0, 0], [ 0, 0, 0, 9, 0, 0, 4, 0, 0, 0]])
将稀疏矩阵保存为 CSR(Compressed Sparse Row)/CSC(Compressed Sparse Column) 格式
np.vstack([rows, cols])
array([[0, 0, 0, 1, 1, 1, 2, 3, 3, 4, 4, 5, 5, 5, 5, 5, 6, 6, 7, 7, 8, 9, 9], [0, 4, 9, 2, 4, 9, 8, 3, 7, 1, 5, 1, 2, 3, 5, 6, 1, 9, 2, 6, 4, 3, 6]])
indptr = np.r_[np.searchsorted(rows, np.unique(rows)), len(rows)]
indptr
array([ 0, 3, 6, 7, 9, 11, 16, 18, 20, 21, 23])
X3 = sparse.csr_matrix((vals, cols, indptr))
X3
<10x10 sparse matrix of type '<class 'numpy.int64'>' with 23 stored elements in Compressed Sparse Row format>
print(X3)
(0, 0) 8 (0, 4) 9 (0, 9) 3 (1, 2) 9 (1, 4) 7 (1, 9) 4 (2, 8) 8 (3, 3) 9 (3, 7) 4 (4, 1) 5 (4, 5) 7 (5, 1) 8 (5, 2) 8 (5, 3) 1 (5, 5) 1 (5, 6) 4 (6, 1) 18 (6, 9) 4 (7, 2) 4 (7, 6) 5 (8, 4) 5 (9, 3) 9 (9, 6) 4
X3.todense()
matrix([[ 8, 0, 0, 0, 9, 0, 0, 0, 0, 3], [ 0, 0, 9, 0, 7, 0, 0, 0, 0, 4], [ 0, 0, 0, 0, 0, 0, 0, 0, 8, 0], [ 0, 0, 0, 9, 0, 0, 0, 4, 0, 0], [ 0, 5, 0, 0, 0, 7, 0, 0, 0, 0], [ 0, 8, 8, 1, 0, 1, 4, 0, 0, 0], [ 0, 18, 0, 0, 0, 0, 0, 0, 0, 4], [ 0, 0, 4, 0, 0, 0, 5, 0, 0, 0], [ 0, 0, 0, 0, 5, 0, 0, 0, 0, 0], [ 0, 0, 0, 9, 0, 0, 4, 0, 0, 0]])
X4 = X2.tocsr()
X4
<10x10 sparse matrix of type '<class 'numpy.longlong'>' with 23 stored elements in Compressed Sparse Row format>
print(X4)
(0, 0) 8 (0, 4) 9 (0, 9) 3 (1, 2) 9 (1, 4) 7 (1, 9) 4 (2, 8) 8 (3, 3) 9 (3, 7) 4 (4, 1) 5 (4, 5) 7 (5, 1) 8 (5, 2) 8 (5, 3) 1 (5, 5) 1 (5, 6) 4 (6, 1) 18 (6, 9) 4 (7, 2) 4 (7, 6) 5 (8, 4) 5 (9, 3) 9 (9, 6) 4
X5 = X2.tocsc()
X5
<10x10 sparse matrix of type '<class 'numpy.longlong'>' with 23 stored elements in Compressed Sparse Column format>
print(X5)
(0, 0) 8 (4, 1) 5 (5, 1) 8 (6, 1) 18 (1, 2) 9 (5, 2) 8 (7, 2) 4 (3, 3) 9 (5, 3) 1 (9, 3) 9 (0, 4) 9 (1, 4) 7 (8, 4) 5 (4, 5) 7 (5, 5) 1 (5, 6) 4 (7, 6) 5 (9, 6) 4 (3, 7) 4 (2, 8) 8 (0, 9) 3 (1, 9) 4 (6, 9) 4
coo_matrix会默认将重复元素求和,适合构造多分类模型的混淆矩阵
rows = np.repeat([0, 1], 4)
cols = np.repeat([0, 1], 4)
vals = np.arange(8)
rows, cols, vals
(array([0, 0, 0, 0, 1, 1, 1, 1]), array([0, 0, 0, 0, 1, 1, 1, 1]), array([0, 1, 2, 3, 4, 5, 6, 7]))
X6 = sparse.coo_matrix((vals, (rows, cols)))
X6.todense()
matrix([[ 6, 0], [ 0, 22]])
y_true = np.random.randint(0, 2, 100)
y_pred = np.random.randint(0, 2, 100)
vals = np.ones(100).astype("int")
y_true, y_pred
(array([0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1]), array([0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0]))
vals.shape, y_true.shape, y_pred.shape
((100,), (100,), (100,))
X7 = sparse.coo_matrix((vals, (y_true, y_pred)))
X7.todense()
matrix([[21, 23], [30, 26]])
from sklearn.metrics import confusion_matrix
confusion_matrix(y_true, y_pred)
array([[21, 23], [30, 26]])
y_true = ["cat", "ant", "cat", "cat", "ant", "bird"]
y_pred = ["ant", "ant", "cat", "cat", "ant", "cat"]
confusion_matrix(y_true, y_pred, labels=["ant", "bird", "cat"])
array([[2, 0, 0], [0, 0, 1], [1, 0, 2]])
Pandas提供了一系列向量化字符串操作(vectorized string operation),极大地提高了字符串清洗效率。Pandas为包含字符串的Series
和Index
对象提供了str
属性,既可以处理字符串,又可以处理缺失值。
所有Python内置的字符串方法都被复制到Pandas的向量化字符串方法中:
len()
| lower()
| translate()
| islower()
ljust()
| upper()
| startswith()
| isupper()
rjust()
| find()
| endswith()
| isnumeric()
center()
| rfind()
| isalnum()
| isdecimal()
zfill()
| index()
| isalpha()
| split()
strip()
| rindex()
| isdigit()
| rsplit()
rstrip()
| capitalize()
| isspace()
| partition()
lstrip()
| swapcase()
| istitle()
| rpartition()
这些方法的返回值并不完全相同,例如
lower()
方法返回字符串,len()
方法返回数值,startswith('T')
返回布尔值,split()
方法返回列表
monte = pd.Series(
(
"Gerald R. Ford",
"James Carter",
"Ronald Reagan",
"George H. W. Bush",
"William J. Clinton",
"George W. Bush",
"Barack Obama",
"Donald J. Trump",
)
)
monte.str.len()
0 14 1 12 2 13 3 17 4 18 5 14 6 12 7 15 dtype: int64
有一些方法支持正则表达式处理字符串。下面是Pandas根据Python标准库的re
模块函数实现的API:
方法 | 描述 |
---|---|
match() |
对每个元素调用re.match() ,返回布尔类型值 |
extract() |
对每个元素调用re.match() ,返回匹配的字符串组(groups ) |
findall() |
对每个元素调用re.findall() |
replace() |
用正则模式替换字符串 |
contains() |
对每个元素调用re.search() ,返回布尔类型值 |
count() |
计算符合正则模式的字符串的数量 |
split() |
等价于str.split() ,支持正则表达式 |
rsplit() |
等价于str.rsplit() ,支持正则表达式 |
monte.str.extract('([A-Za-z]+)')
0 | |
---|---|
0 | Gerald |
1 | James |
2 | Ronald |
3 | George |
4 | William |
5 | George |
6 | Barack |
7 | Donald |
找出所有开头和结尾都是辅音字母的名字——这可以用正则表达式中的开始符号(^
)与结尾符号($
)来实现:
monte.str.findall(r'^[^AEIOU].*[^aeiou]$')
0 [Gerald R. Ford] 1 [James Carter] 2 [Ronald Reagan] 3 [George H. W. Bush] 4 [William J. Clinton] 5 [George W. Bush] 6 [] 7 [Donald J. Trump] dtype: object
还有其他一些方法可以实现更方便的操作
方法 | 描述 |
---|---|
get() |
获取元素索引位置上的值,索引从0开始 |
slice() |
对元素进行切片取值 |
slice_replace() |
对元素进行切片替换 |
cat() |
连接字符串(此功能比较复杂,建议阅读文档) |
repeat() |
重复元素 |
normalize() |
将字符串转换为Unicode规范形式 |
pad() |
在字符串的左边、右边或两边增加空格 |
wrap() |
将字符串按照指定的宽度换行 |
join() |
用分隔符连接Series 的每个元素 |
get_dummies() |
按照分隔符提取每个元素的dummy 变量,转换为独热(one-hot)编码的DataFrame |
get()
与slice()
操作可以从每个字符串数组中获取向量化元素。例如,我们可以通过str.slice(0, 3)
获取每个字符串数组的前3个字符,df.str.slice(0, 3)
=df.str[0:3]
,df.str.get(i)
=df.str[i]
monte.str[0:3]
0 Ger 1 Jam 2 Ron 3 Geo 4 Wil 5 Geo 6 Bar 7 Don dtype: object
get()
与slice()
操作还可以在split()
操作之后使用。例如,要获取每个姓名的姓(last name),可以结合使用split()
与get()
:
monte.str.split().str.get(-1)
0 Ford 1 Carter 2 Reagan 3 Bush 4 Clinton 5 Bush 6 Obama 7 Trump dtype: object
get_dummies()
方法可以快速将指标变量分割成一个独热编码的DataFrame
(每个元素都是0或1),如A=出生在美国、B=出生在英国、C=喜欢奶酪、D=喜欢午餐肉:
full_monte = pd.DataFrame(
{
"name": monte,
"info": ["B|C|D", "B|D", "A|C", "B|D", "B|C", "A|C", "B|D", "B|C|D"],
}
)
full_monte
name | info | |
---|---|---|
0 | Gerald R. Ford | B|C|D |
1 | James Carter | B|D |
2 | Ronald Reagan | A|C |
3 | George H. W. Bush | B|D |
4 | William J. Clinton | B|C |
5 | George W. Bush | A|C |
6 | Barack Obama | B|D |
7 | Donald J. Trump | B|C|D |
full_monte['info'].str.get_dummies('|')
A | B | C | D | |
---|---|---|---|---|
0 | 0 | 1 | 1 | 1 |
1 | 0 | 1 | 0 | 1 |
2 | 1 | 0 | 1 | 0 |
3 | 0 | 1 | 0 | 1 |
4 | 0 | 1 | 1 | 0 |
5 | 1 | 0 | 1 | 0 |
6 | 0 | 1 | 0 | 1 |
7 | 0 | 1 | 1 | 1 |
date = pd.to_datetime("4th of May, 2020")
date
Timestamp('2020-05-04 00:00:00')
date.strftime('%A')
'Monday'
可以直接进行NumPy类型的向量化运算:
date + pd.to_timedelta(np.arange(12), 'D')
DatetimeIndex(['2020-05-04', '2020-05-05', '2020-05-06', '2020-05-07', '2020-05-08', '2020-05-09', '2020-05-10', '2020-05-11', '2020-05-12', '2020-05-13', '2020-05-14', '2020-05-15'], dtype='datetime64[ns]', freq=None)
index = pd.DatetimeIndex(["2019-01-04", "2019-02-04", "2020-03-04", "2020-04-04"])
data = pd.Series([0, 1, 2, 3], index=index)
data
2019-01-04 0 2019-02-04 1 2020-03-04 2 2020-04-04 3 dtype: int64
直接用日期进行切片取值:
data['2020-02-04':'2020-04-04']
2020-03-04 2 2020-04-04 3 dtype: int64
直接通过年份切片获取该年的数据:
data['2020']
2020-03-04 2 2020-04-04 3 dtype: int64
本节将介绍Pandas用来处理时间序列的基础数据类型。
Timestamp
类型。本质上是Python的原生datetime
类型的替代品,但是在性能更好的numpy.datetime64
类型的基础上创建。对应的索引数据结构是DatetimeIndex
。Period
类型。这是利用numpy.datetime64
类型将固定频率的时间间隔进行编码。对应的索引数据结构是PeriodIndex
。Timedelta
类型。Timedelta
是一种代替Python原生datetime.timedelta
类型的高性能数据结构,同样是基于numpy.timedelta64
类型。对应的索引数据结构是TimedeltaIndex
。最基础的日期/时间对象是Timestamp
和DatetimeIndex
,对pd.to_datetime()
传递一个日期会返回一个Timestamp
类型,传递一个时间序列会返回一个DatetimeIndex
类型:
from datetime import datetime
dates = pd.to_datetime(
[datetime(2020, 7, 3), "4th of July, 2020", "2020-Jul-6", "07-07-2020", "20200708"]
)
dates
DatetimeIndex(['2020-07-03', '2020-07-04', '2020-07-06', '2020-07-07', '2020-07-08'], dtype='datetime64[ns]', freq=None)
任何DatetimeIndex
类型都可以通过to_period()
方法和一个频率代码转换成PeriodIndex
类型。
用'D'
将数据转换成单日的时间序列:
dates.to_period('D')
PeriodIndex(['2020-07-03', '2020-07-04', '2020-07-06', '2020-07-07', '2020-07-08'], dtype='period[D]', freq='D')
当用一个日期减去另一个日期时,返回的结果是TimedeltaIndex
类型:
dates - dates[0]
TimedeltaIndex(['0 days', '1 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq=None)
pd.date_range()
¶为了能更简便地创建有规律的时间序列,Pandas提供了一些方法:pd.date_range()
可以处理时间戳、pd.period_range()
可以处理周期、pd.timedelta_range()
可以处理时间间隔。通过开始日期、结束日期和频率代码(同样是可选的)创建一个有规律的日期序列,默认的频率是天:
pd.date_range('2020-07-03', '2020-07-10')
DatetimeIndex(['2020-07-03', '2020-07-04', '2020-07-05', '2020-07-06', '2020-07-07', '2020-07-08', '2020-07-09', '2020-07-10'], dtype='datetime64[ns]', freq='D')
日期范围不一定非是开始时间与结束时间,也可以是开始时间与周期数periods
:
pd.date_range('2020-07-03', periods=8)
DatetimeIndex(['2020-07-03', '2020-07-04', '2020-07-05', '2020-07-06', '2020-07-07', '2020-07-08', '2020-07-09', '2020-07-10'], dtype='datetime64[ns]', freq='D')
通过freq
参数改变时间间隔,默认值是D
。例如,可以创建一个按小时变化的时间戳:
pd.date_range('2020-07-03', periods=8, freq='H')
DatetimeIndex(['2020-07-03 00:00:00', '2020-07-03 01:00:00', '2020-07-03 02:00:00', '2020-07-03 03:00:00', '2020-07-03 04:00:00', '2020-07-03 05:00:00', '2020-07-03 06:00:00', '2020-07-03 07:00:00'], dtype='datetime64[ns]', freq='H')
如果要创建一个有规律的周期或时间间隔序列,有类似的函数pd.period_range()
和pd.timedelta_range()
。下面是一个以月为周期的示例:
pd.period_range('2020-07', periods=8, freq='M')
PeriodIndex(['2020-07', '2020-08', '2020-09', '2020-10', '2020-11', '2020-12', '2021-01', '2021-02'], dtype='period[M]', freq='M')
以小时递增:
pd.timedelta_range(0, periods=10, freq='H')
TimedeltaIndex(['00:00:00', '01:00:00', '02:00:00', '03:00:00', '04:00:00', '05:00:00', '06:00:00', '07:00:00', '08:00:00', '09:00:00'], dtype='timedelta64[ns]', freq='H')
Pandas时间序列工具的基础是时间频率或偏移量(offset)代码。就像之前见过的D
(day)和H
(hour)代码,可以设置任意需要的时间间隔
代码 | 描述 | 代码 | 描述 |
---|---|---|---|
D |
天(calendar day,按日历算,含双休日) | B |
天(business day,仅含工作日) |
W |
周(weekly) | ||
M |
月末(month end) | BM |
月末(business month end,仅含工作日) |
Q |
季节末(quarter end) | BQ |
季节末(business quarter end,仅含工作日) |
A |
年末(year end) | BA |
年末(business year end,仅含工作日) |
H |
小时(hours) | BH |
小时(business hours,工作时间) |
T |
分钟(minutes) | ||
S |
秒(seconds) | ||
L |
毫秒(milliseonds) | ||
U |
微秒(microseconds) | ||
N |
纳秒(nanoseconds) |
月、季、年频率都是具体周期的结束时间(月末、季末、年末),而有一些以S
(start,开始) 为后缀的代码表示日期开始。
代码 | 频率 |
---|---|
MS |
月初(month start) |
BMS |
月初(business month start,仅含工作日) |
QS |
季初(quarter start) |
BQS |
季初(business quarter start,仅含工作日) |
AS |
年初(year start) |
BAS |
年初(business year start,仅含工作日) |
另外,可以在频率代码后面加三位月份缩写字母来改变季、年频率的开始时间:
Q-JAN
、BQ-FEB
、QS-MAR
、BQS-APR
等A-JAN
、BA-FEB
、AS-MAR
、BAS-APR
等也可以在后面加三位星期缩写字母来改变一周的开始时间:
W-SUN
、W-MON
、W-TUE
、W-WED
等还可以将频率组合起来创建的新的周期。例如,可以用小时(H
)和分钟(T
)的组合来实现2小时30分钟:
pd.timedelta_range(0, periods=9, freq="2H30T")
TimedeltaIndex(['00:00:00', '02:30:00', '05:00:00', '07:30:00', '10:00:00', '12:30:00', '15:00:00', '17:30:00', '20:00:00'], dtype='timedelta64[ns]', freq='150T')
所有这些频率代码都对应Pandas时间序列的偏移量,具体内容可以在pd.tseries.offsets
模块中找到。例如,可以用下面的方法直接创建一个工作日偏移序列:
from pandas.tseries.offsets import BDay
pd.date_range('2020-07-01', periods=5, freq=BDay())
DatetimeIndex(['2020-07-01', '2020-07-02', '2020-07-03', '2020-07-06', '2020-07-07'], dtype='datetime64[ns]', freq='B')
下面用贵州茅台的历史股票价格演示:
%%file snowball.py
from datetime import datetime
import requests
import pandas as pd
def get_stock(code):
response = requests.get(
"https://stock.xueqiu.com/v5/stock/chart/kline.json",
headers={
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36"
},
params=(
("symbol", code),
("begin", int(datetime.now().timestamp() * 1000)),
("period", "day"),
("type", "before"),
("count", "-5000"),
("indicator", "kline"),
),
cookies={"xq_a_token": "328f8bbf7903261db206d83de7b85c58e4486dda",},
)
if response.ok:
d = response.json()["data"]
data = pd.DataFrame(data=d["item"], columns=d["column"])
data.index = data.timestamp.apply(
lambda _: pd.Timestamp(_, unit="ms", tz="Asia/Shanghai")
)
return data
else:
print("stock error")
Writing snowball.py
from snowball import get_stock
data = get_stock("SH600519") # 贵州茅台
data.tail()
timestamp | volume | open | high | low | close | chg | percent | turnoverrate | amount | volume_post | amount_post | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
timestamp | ||||||||||||
2020-05-08 00:00:00+08:00 | 1588867200000 | 2907868 | 1317.0 | 1338.00 | 1308.51 | 1314.61 | 2.61 | 0.20 | 0.23 | 3.839218e+09 | None | None |
2020-05-11 00:00:00+08:00 | 1589126400000 | 2367119 | 1320.0 | 1335.00 | 1313.67 | 1323.01 | 8.40 | 0.64 | 0.19 | 3.135991e+09 | None | None |
2020-05-12 00:00:00+08:00 | 1589212800000 | 1972181 | 1318.0 | 1334.99 | 1316.00 | 1333.00 | 9.99 | 0.76 | 0.16 | 2.621825e+09 | None | None |
2020-05-13 00:00:00+08:00 | 1589299200000 | 2201431 | 1333.0 | 1337.99 | 1322.88 | 1335.95 | 2.95 | 0.22 | 0.18 | 2.931465e+09 | None | None |
2020-05-14 00:00:00+08:00 | 1589385600000 | 1857492 | 1330.0 | 1334.88 | 1325.11 | 1326.59 | -9.36 | -0.70 | 0.15 | 2.467976e+09 | None | None |
gzmt = data['close']
gzmt.plot(figsize=(15,8));
plt.title('贵州茅台历史收盘价');
按照新的频率(更高频率、更低频率)对数据进行重采样,可以通过resample()
方法、asfreq()
方法。
resample()
方法是以数据累计(data aggregation)为基础asfreq()
方法是以数据选择(data selection)为基础。用两种方法对数据进行下采样(down-sample,减少采样频率,从日到月)。用每年末('BA'
,最后一个工作日)对数据进行重采样:
plt.figure(figsize=(15, 8))
gzmt.plot(alpha=0.5, style="-")
gzmt.resample("BA").mean().plot(style=":")
gzmt.asfreq("BA").plot(style="--")
plt.title('贵州茅台历史收盘价年末采样');
plt.legend(["input", "resample", "asfreq"], loc="upper left");
在每个数据点上,
resample
反映的是上一年的均值,而asfreq
反映的是上一年最后一个工作日的收盘价。在进行上采样(up-sampling,增加采样频率,从月到日)时,
resample()
与asfreq()
的用法大体相同,
两种方法都默认将采样作为缺失值NaN
,与pd.fillna()
函数类似,asfreq()
有一个method
参数可以设置填充缺失值的方式。对数据按天进行重采样(包含周末),asfreq()
向前填充与向后填充缺失值的结果对比:
fig, ax = plt.subplots(2, sharex=True, figsize=(15, 8))
data = gzmt.iloc[-14:]
ax[0].set_title("贵州茅台近两周收盘价")
data.asfreq("D").plot(ax=ax[0], marker="o")
ax[1].set_title("采样缺失值填充方法对比")
data.asfreq("D", method="bfill").plot(ax=ax[1], style="-o")
data.asfreq("D", method="ffill").plot(ax=ax[1], style="--o")
ax[1].legend(["back-fill", "forward-fill"]);
fig, ax = plt.subplots(3, sharey=True, figsize=(15, 8))
# 对数据应用时间频率,用向后填充解决缺失值
gzmt = gzmt.asfreq("D", method="pad")
gzmt.plot(ax=ax[0])
gzmt.shift(900).plot(ax=ax[1])
gzmt.tshift(900).plot(ax=ax[2])
# 设置图例与标签
local_max = pd.to_datetime("2010-01-01")
offset = pd.Timedelta(900, "D")
ax[0].legend(["input"], loc=2)
ax[0].get_xticklabels()[5].set(weight="heavy", color="red")
ax[0].axvline(local_max, alpha=0.3, color="red")
ax[1].legend(["shift(900)"], loc=2)
ax[1].get_xticklabels()[5].set(weight="heavy", color="red")
ax[1].axvline(local_max + offset, alpha=0.3, color="red")
ax[2].legend(["tshift(900)"], loc=2)
ax[2].get_xticklabels()[1].set(weight="heavy", color="red")
ax[2].axvline(local_max + offset, alpha=0.3, color="red");