跳到主要内容

数据科学入门

matplotlib模块

二维直线图

import numpy as np
from matplotlib import pyplot as plt
# 生成一个-3到3的等差数列,共100个数
a = np.linspace(-3, 3, 10)

# 三角函数
b = np.sin(a)
plt.plot(a, b)
# 等价于 plt.plot(b)
plt.show() # 正弦图

绘制多条数据线

# 画出多条数据线:
plt.plot(a, np.sin(a))
plt.plot(a, np.sin(2 * a))
plt.show()

线条修饰

# 使用字符串,给定线条参数:
# b:蓝色
# -- : 虚线
# o : 圆点
'''
完整参数可参考
https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.plot.html#matplotlib.pyplot.plot
'''
plt.plot(a, np.sin(a), 'b--o')
plt.show()

散点图

plt.plot(a, np.sin(a), 'bo')
plt.show() # 二维散点图
# 等价于
plt.scatter(a, np.sin(a),color='blue',marker='o')
plt.show()
t = np.linspace(0, 2 * np.pi, 50)
x = np.sin(t)
plt.plot(t, x, 'bo', t, np.sin(2 * t), 'r-^', label='sin', color='red', )
plt.legend()
plt.xlabel('radians')
plt.ylabel('amplitude', fontsize='large')
plt.title('Sin(x)')
plt.grid()
plt.show()
# 直方图
data = np.array([1234, 321, 400, 120, 11, 30, 2000])
plt.hist(data, 7)
plt.show()

绘制三维数据

# 高维 RBF 插值
# 三维数据点:
import numpy as np
from matplotlib import pyplot as plt
x, y = np.mgrid[-np.pi / 2:np.pi / 2:5j, -np.pi / 2:np.pi / 2:5j]
z = np.cos(np.sqrt(x ** 2 + y ** 2))
fig = plt.figure(figsize=(12, 6))
ax = fig.add_subplot(projection='3d')
ax.scatter(x, y, z)
fig.savefig("mplot3d.jpg")
plt.show()

直方图

import numpy as np
from matplotlib import pyplot as plt
# 正态分布
from scipy.stats import norm
x_norm = norm.rvs(size=500)
x_norm.shape

plt.ion() #开启interactive mode

h = plt.hist(x_norm)
print('counts, ', h[0])
print('bin centers', h[1])
figure = plt.figure(1) # 创建图表1
plt.show()

归一化直方图(用出现频率代替次数),将划分区间变为 20(默认 10):

h = plt.hist(x_norm, bins=20)
plt.show()

多组直方图

from scipy.stats import norm
from scipy.stats import ttest_ind

# 独立样本 t 检验
# 两组参数不同的正态分布:
n1 = norm(loc=0.3, scale=1.0)
n2 = norm(loc=0, scale=1.0)

# 从分布中产生两组随机样本:
n1_samples = n1.rvs(size=100)
n2_samples = n2.rvs(size=100)

# 将两组样本混合在一起:
samples = np.hstack((n1_samples, n2_samples))

# 最大似然参数估计:
loc, scale = norm.fit(samples)
n = norm(loc=loc, scale=scale)

# 比较:
x = np.linspace(-3, 3, 100)

plt.hist([samples, n1_samples, n2_samples])
plt.plot(x, n.pdf(x), 'b-')
plt.plot(x, n1.pdf(x), 'g-')
plt.plot(x, n2.pdf(x), 'r-')
plt.show()

离散分布

# 离散分布
from scipy.stats import randint

# 离散均匀分布的概率质量函数(PMF):
high = 10
low = -10

x = np.arange(low, high + 1, 0.5)
p = plt.stem(x, randint(low, high).pmf(x)) # 杆状图
plt.show()

图内填充

from scipy.integrate import trapz

x1 = np.linspace(-2, 2, 108)
p = trapz(norm.pdf(x1), x1)
print('{:.2%} of the values lie between -2 and 2'.format(p))

plt.fill_between(x1, norm.pdf(x1), color='red')
plt.plot(x, norm.pdf(x), 'k-')
plt.show()

Numpy数组

数组:array

很多其他科学计算的第三方库都是以Numpy为基础建立的。

Numpy的一个重要特性是它的数组计算。

使用前一定要先导入 Numpy 包,导入的方法有以下几种:

import numpy
import numpy as np
from numpy import *
from numpy import array, sin

导入numpy,最常用为这种:

import numpy as np

假如我们想将列表中的每个元素增加1,但列表不支持这样的操作(报错):

a = [1, 2]

# a + 1 # 报错

使用numpy.array:

a = np.array(a)
a # [1 2]
b = a + 1
b # array([2,3])

与另一个 array 相加,得到对应元素相加的结果:

c = a + b
print(c) # array([3,5])

# 对应元素相乘:
print(a * b) # [2 6]

# 对应元素乘方:
print(a ** b) # [1 8]

数组的合并

import numpy as np

a = np.array([[1, 2], [3, 4]])
b = np.arange(2) # np.array([0, 1])
print(a, b)
'''[[1 2]
[3 4]] [0 1]'''

# 正确的做法是:
np.append(a, b) # array([1, 2, 3, 4, 0, 1])

# 错误的做法是:
print(list(a)+ list(b)) # [array([1, 2]), array([3, 4]), 0, 1]

提取数组中的元素

# 提取第一个
a = np.array([1, 2, 3, 4])
print(a[0]) # 1

# 提取前两个元素:
print(a[:2]) # [1 2]

# 最后两个元素
print(a[-2:]) # [3 4]

# 相加:
print(a[:2] + a[-2:]) # [4 6]

修改数组形状

查看array的形状:

b = a.shape
b # (4,)
# 修改 array 的形状:
a.shape = 2, 2
a
# [[1 2]
# [3 4]]
# 多维数组
# a 现在变成了一个二维的数组,可以进行加法:
a + a
# [[2 4]
# [6 8]]
# 乘法仍然是对应元素的乘积,并不是按照矩阵乘法来计算:
a * a
# [[ 1 4]
# [ 9 16]]

Numpy索引:index

import numpy as np

# 查看形状,会返回一个元组,每个元素代表这一维的元素数目:
a = np.array([1, 2, 3, 5])
# 1维数组,返回一个元组
a.shape
# 查看元素数目:
a.size

使用fill方法设定初始值

可以使用 fill 方法将数组设为指定值:

print(a)
a.fill(-4)
print(a)
# 切片,支持负索引:
a = np.array([11, 12, 13, 14, 15])
print(a[1:-2]) # [12 13]
# 省略参数:
print(a[::2]) # [11 13 15]
print(a[-2:]) # array([14, 15])

假设我们记录一辆汽车表盘上每天显示的里程数:

rec = np.array([21000, 21180, 21240, 22100, 22400])
dist = rec[1:] - rec[:-1] # 后一天减去前一天的
dist

多维数组的索引

a = np.array([[1, 2, 3], [7, 8, 9]])
a
# 查看形状:
print(a.shape)

# 查看总的元素个数:
print(a.size)

# 查看维数:
print(a.ndim)

# 对于二维数组,可以传入两个数字来索引:
print(a[1, 1])

# 索引一整行内容:
print(a[0])

多维数组的复杂一点的例子:

a = np.array([[0, 1, 2, 3, 4, 5],
[10, 11, 12, 13, 14, 15],
[20, 21, 22, 23, 24, 25],
[30, 31, 32, 33, 34, 35],
[40, 41, 42, 43, 44, 45],
[50, 51, 52, 53, 54, 55]])

# 想得到第一行的第 4 和第 5 两个元素:
print(a[0, 3:5]) # [3 4]

# 得到最后两行的最后两列:
print(a[4:, 4:]) # [[44 45][54 55]]

# 得到第三列:
print(a[:, 2]) # [ 2 12 22 32 42 52]

取出3,5行的奇数列:

b = a[2::2, ::2]
b

切片在内存中使用的是引用机制

引用机制意味着,Python并没有为 b 分配新的空间来存储它的值, 而是让 b 指向了 a 所分配的内存空间,因此,改变 b 会改变 a 的值:

a = np.array([0, 1, 2, 3, 4])
b = a[2:4]

b
b[0] = 10

b
a
# 而这种现象在列表中并不会出现:
b = a[2:3]
b[0] = 12
print(a)

# 解决方法是使用copy()方法产生一个复制,这个复制会申请新的内存:
b = a[2:4].copy()
b[0] = 10
print(a, b)

一维花式索引

与 range 函数类似,我们可以使用 arange 函数来产生等差数组。

a = np.arange(0, 80, 10)
a
# 花式索引需要指定索引位置:
indices = [1, 2, -3]
y = a[indices]

y
# 还可以使用布尔数组来花式索引:
mask = np.array([0, 1, 1, 0, 0, 1, 0, 1], dtype=bool)
a[mask] # [10 20 50 70]

选出了所有大于0.5的值:

from numpy.random import rand

a = rand(10)
a
mask = a > 0.5
a[mask]

“不完全”索引

只给定行索引的时候,返回整行:

a = np.array([[0, 1, 2, 3, 4, 5],
[10, 11, 12, 13, 14, 15],
[20, 21, 22, 23, 24, 25],
[30, 31, 32, 33, 34, 35],
[40, 41, 42, 43, 44, 45],
[50, 51, 52, 53, 54, 55]])
b = a[:3]
b
# 这时候也可以使用花式索引取出第2,3,5行:
condition = np.array([0, 1, 1, 0, 1, 0], dtype=bool)
c = a[condition]
c

where语句

where(array)

where 函数会返回所有非零元素的索引。

a = np.array([1, 2, 4, 6])
a > 2 # [False False True True]
b = np.where(a > 2)
b # 返回的是索引位置
# 注意到 where 的返回值是一个元组。
index = np.where(a > 2)[0]
print(index) # [2 3]

# 可以直接用 where 的返回值进行索引:
loc = np.where(a > 2)
b = a[loc]
print(b) # [4 6]

考虑二维数组:

a = np.array([[0, 12, 5, 20],
[1, 2, 11, 15]])
loc = np.where(a > 10)
print(loc) # (array([0, 0, 1, 1]), array([1, 3, 2, 3]))

# 也可以直接用来索引a:
b = a[loc]
print(b) # [12 20 11 15]

或者可以这样:

rows, cols = np.where(a > 10)
print(rows)
print(cols)
print(a[rows, cols])

例子:

a = np.arange(20)
a.shape = 5, 4
a
a > 12
b = np.where(a > 12)
b
# (array([3, 3, 3, 4, 4, 4, 4]), array([1, 2, 3, 0, 1, 2, 3]))
a[b]  # [13 14 15 16 17 18 19]

Numpy方法

Numpy的常用方法。

import numpy as np

a = np.array([[0, 1, 2, 3], [4, 5, 6, 7]])
a
for row in a:
print(row)

所有元素的迭代器:

for i in a.flat:
print(i)

矩阵转置

print(a)
print(a.T)
print(a)
print(a.shape) # 数组形状 (m,n,o,...)
print(a.size)  # 数组元素数
a.resize((4, 2))
print(a)
print(a.shape)

squeeze

把shape为1的维度去掉:

a = np.arange(10).reshape(1,10)
a
a.shape
b = np.squeeze(a)
b
b.shape

再举个多维的例子:

a = np.arange(10).reshape(1, 2, 5)
print(a)

print(a.shape)
b = np.squeeze(a)
b.shape
a.shape

复制

a = np.array([[0, 1, 2, 3], [4, 5, 6, 7]])
b = a.copy()
b[0][0] = -1

b

填充

b.fill(9)
b
# 转化为列表:
a.tolist()

复数

# 实部:
b = np.array([1 + 2j, 3 + 4j, 5 + 6j])
c = b.real
print(c)

# 虚部:
d = b.imag
print(d)
# 共轭:
print(b.conj())

# 保存成文本:
a.dump("file.txt")


import os
os.path.exists('file.txt')
with open('file.txt', 'rb') as f:
m = f.read()
m
# 字符串:
a.dumps()
# 写入文件
a.tofile('foo.csv', sep=',', format="%s")
os.path.exists('foo.csv')
with open('foo.csv') as f:
m = f.read()
m

排序

非零元素的索引:

b = a.nonzero()
a
b
# 排序:
b = np.array([3, 2, 7, 4, 1])
b.sort()
b
# 排序的索引位置:
b = np.array([2, 3, 1])
b.argsort(axis=-1) # array([2, 0, 1])
# 将 b 插入 a 中的索引,使得 a 保持有序:
a = np.array([1, 3, 4, 6])
b = np.array([0, 2, 5])
print(a.searchsorted(b))

元素的数学操作

clip,限制在一定范围:

a = np.array([[4, 1, 3], [2, 1, 5]])
a.clip(0, 2)
a
# 近似:
a = np.array([1.344, 2.449, 2.558])
b = a.round(decimals=2)
b # [ 1.34 2.45 2.56]
# 是否全部非零:
print(a.all())
import os

os.remove('foo.csv')
os.remove('file.txt')

数组与字符串的转换

tobytes 函数

a = np.array([[1, 2], [3, 4]], dtype=np.uint8)
print(a)
print(a.tobytes())

frombuffer 函数

可以使用 frombuffer 函数从字符串中读出数据,不过要指定类型:

s = a.tobytes()
b = np.frombuffer(s, dtype=np.uint8)
b

此时,返回的数组是一维的,需要重新设定维度:

b.shape = 2, 2
b
# 可以使用reshape:
b = np.frombuffer(s, dtype=np.uint8).reshape(2, 2)
b

文本中读取数组

对于读文本文件,推荐使用:

  • loadtxt
  • genfromtxt
  • savetxt

对于二进制文本文件,推荐使用

  • save
  • load
  • savez

loadtxt 函数

loadtxt(fname, dtype=<type 'float'>,
comments='#', delimiter=None,
converters=None, skiprows=0,
usecols=None, unpack=False, ndmin=0)
  • loadtxt 有很多可选参数,其中 delimiter 就是刚才用到的分隔符参数。
  • skiprows 参数表示忽略开头的行数,可以用来读写含有标题的文本
data_file = "../data/numpy/data.txt"
c = np.loadtxt(data_file, dtype=int)
c
c.shape

genfromtxt

genfromtxt 函数功能更为全面, 能处理更多的情况,但相应的速度和效率会慢一些。

help(np.genfromtxt)
g = np.genfromtxt(data_file)
g

当然,还有很笨的写法:

首先将数据转化成一个列表组成的列表,再将这个列表转换为数组:

data = []

with open(data_file) as f:
# 每次读一行
for line in f:
fileds = line.split()
row_data = [float(x) for x in fileds]
data.append(row_data)

data = np.array(data)
data
# loadtxt 的更多特性
sp_file = '../data/numpy/special_data.txt'
data = np.loadtxt(sp_file,
dtype=int,
comments='%', # 百分号为注释符
delimiter=',', # 逗号分割
skiprows=1, # 忽略第一行
usecols=(0, 1, 2, 4)) # 指定使用哪几列数据
data

loadtxt 自定义转换方法

loadtxt返回的值为字节字符串bytes, 对字符串解码用函数decode(‘asii’),变成str格式:

import datetime


def date_converter(s):
return datetime.datetime.strptime(s.decode('ascii'), "%Y-%m-%d")

date_file = '../data/numpy/datetime_data.txt'
data = np.loadtxt(date_file,
dtype=object, # 数据类型为对象
converters={0: date_converter, # 第一列使用自定义转换方法
1: float, # 第二第三列使用浮点数转换
2: float})

data

将数组写入文件

savetxt 可以将数组写入文件,默认使用科学计数法的形式保存:

a = np.array([[1, 2, 3], [5, 6, 7]])
np.savetxt('out.txt', a)

# 可以用类似printf 的方式指定输出的格式:
a = np.array([[1, 2, 3], [5, 6, 7]])
print(a.shape)

np.savetxt('out_fmt.txt', a, fmt=['%d'] * a.shape[1], newline='\n')
with open('out_fmt.txt') as f:
for line in f:
print(line)
m = zip([1, 2, 3, 4, 5], ['a', 'b', 'c', 'd', 'e'])
m = list(m)

z = np.array(m)
print(z)

np.savetxt('out_str_fmt.txt', z, fmt=['%s'] * z.shape[1])
import os
os.remove('out.txt')
os.remove('out_fmt.txt')
os.remove('out_str_fmt.txt')

Numpy 二进制格式

保存的方法:

  • save(file, arr) 保存单个数组,.npy 格式
  • savez(file, *args, **kwds) 保存多个数组,无压缩的 .npz 格式
  • savez_compressed(file, *args, **kwds) 保存多个数组,有压缩的 .npz 格式
a = np.array([[1, 2], [3, 4]])
np.save('out.npy', a)
# 二进制与文本大小比较
a = np.arange(10000.)
np.savetxt('a.txt', a)
# 查看大小:
import os

print(os.stat('a.txt').st_size)

# 保存为二进制
np.save('a.npy', a)
print(os.stat('a.npy').st_size)

二进制文件大约是文本文件的三分之一。

# 保存多个数组
a = np.array([[1, 2], [3, 4]])
b = np.arange(1000)
print(a)
print(b)
np.savez('ab.npz', a=a, b=b)
# 加载数据
ab = np.load('ab.npz')
print(os.stat('ab.npz').st_size) # file size
print(ab.keys())
print(list(ab.keys()))

print(ab['a'].shape)
print(ab['b'].shape)
np.savez_compressed('ab_compressed.npz', a=a, b=b)
print(os.stat('ab_compressed.npz').st_size) # file size
os.remove('out.npy')
os.remove('a.txt')
os.remove('a.npy')
os.remove('ab.npz')
os.remove('ab_compressed.npz')

生成数组的函数

arange 生成数组,[start,stop)

arange(start, stop=None, step=1, dtype=None)

np.arange(5)  # [0 1 2 3 4]
a = np.arange(0, 2 * np.pi, np.pi / 4)
a

linspace

linspace(start,stop,N)

产生N个等距分布在[start,stop]间的元素组成的数组,包括start,stop

np.linspace(0, 1, 5)  # [ 0.    0.25  0.5   0.75  1.  ]

logspace

logspace(start, stop, N)

产生 N 个对数等距分布的数组,默认以10为底:

np.logspace(0, 1, 5)

产生的值为[100,100.25,100.5,100.75,101]\left[10^0, 10^{0.25},10^{0.5},10^{0.75},10^1\right]

meshgrid

二维平面中生成一个网格

x_ticks = np.linspace(-1, 1, 5)
y_ticks = np.linspace(-1, 1, 5)
x, y = np.meshgrid(x_ticks, y_ticks)
print(x_ticks)
print(x)

图例

import matplotlib.pyplot as plt
from matplotlib import cm


def f(x, y):
# sinc 函数
r = np.sqrt(x ** 2 + y ** 2)
result = np.sin(r) / r
result[r == 0] = 1.0
return result


x_ticks = np.linspace(-10, 10, 51)
y_ticks = np.linspace(-10, 10, 51)

x, y = np.meshgrid(x_ticks, y_ticks, sparse=True)
print(x) # x, y 中有很多冗余的元素,这里提供了一个 sparse 的选项去冗余
z = f(x, y)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(x, y, z,
rstride=1, cstride=1,
cmap=cm.YlGnBu_r)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
plt.show()

内存映射

Numpy 有对内存映射的支持。

内存映射也是一种处理文件的方法,主要的函数有:

  • memmap
  • frombuffer
  • ndarray constructor

使用内存映射文件处理存储于磁盘上的文件时,将不必再对文件执行I/O操作, 使得内存映射文件在处理大数据量的文件时能起到相当重要的作用。

memmap(filename,
dtype=uint8,
mode='r+'
offset=0
shape=None
order=0)

mode 表示文件被打开的类型:

  • r 只读
  • c 复制+写,但是不改变源文件
  • r+ 读写,使用 flush 方法会将更改的内容写入文件
  • w+ 写,如果存在则将数据覆盖

Pandas数据分析

Pandas是Python的一个用于数据分析的库: http://pandas.pydata.org API速查:http://pandas.pydata.org/pandas-docs/stable/api.html

基于NumPy,SciPy的功能,在其上补充了大量的数据操作(Data Manipulation)功能。

统计、分组、排序、透视表自由转换,如果你已经很熟悉结构化数据库(RDBMS)与Excel的功能,就会知道Pandas有过之而无不及!

为什么是Pandas

快速的识别结构化数据

import numpy as np
import scipy as sp
import pandas as pd

iris_file = './iris.data'

data = pd.read_csv(iris_file, header=None, encoding='utf-8')
data

快速的操作元数据

cnames = ['sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'class']
data.columns = cnames
data

快速过滤

data[data['petal_width'] == data.petal_width.max()]

快速切片

data.iloc[::30, :2]

快速统计

data['class'].value_counts()
for x in range(4):
s = data.iloc[:,x]
print('{0:<12}'.format(s.name), " Statistics: ",
'{0:>5} {1:>5} {2:>5} {3:>5}'.format(s.max(), s.min(), round(s.mean(),2),round(s.std(),2)))

快速“MapReduce”

slogs = lambda x:np.log(x)*x
entpy = lambda x:np.exp((slogs(x.sum())-x.map(slogs).sum())/x.sum())
data.groupby('class').agg(entpy)

Pandas的重要数据类型

  • DataFrame(二维表)
  • Series(一维序列)
  • Index(行索引,行级元数据)

1.1 Series:pandas的长枪(数据表中的一列或一行,观测向量,一维数组...)

数据世界中对于任意一个个体的全面观测,或者对于任意一组个体某一属性的观测,全部可以抽象为Series的概念。

用值构建一个Series:

由默认index和values组成。

series1 = pd.Series(np.random.randn(4))
series1
print(type(series1))
print(series1.index)
print(series1.values)

Series支持过滤的原理就如同NumPy

series1 > 0
series1[series1 > 0]

当然也支持Broadcasting

series1*2
series1+5

以及Universal Function

print(series1)
print(np.exp(series1))

#NumPy Universal Function
f_np = np.frompyfunc(lambda x:np.exp(x*2 + 5), 1, 1)
f_np(series1)

在序列上就使用行标,而不是创建一个2列的数据表,能够轻松辨别哪里是数据,哪里是元数据:

series2 = pd.Series(series1.values, index=['norm_' + str(i) for i in range(4)])
print(series2, type(series2))
print(series2.index)
print(type(series2.index))
print(series2.values)
series2

虽然行是有顺序的,但是仍然能够通过行级的index来访问到数据:

(当然也不尽然像Ordered Dict,因为行索引甚至可以重复,不推荐重复的行索引不代表不能用)

series2[['norm_0','norm_3']]
'norm_0' in series2
'norm_6' in series2

默认行索引就像行号一样:

series1.index

从Key不重复的Ordered Dict或者从Dict来定义Series就不需要担心行索引重复:

s_dict = {"Japan":"Tokyo", "Korea":"Seoul", "China":"Beijing"}
series3 = pd.Series(s_dict)

print(series3.index)
print(series3.values)
series3

与Dict区别一: 有序

lst = ["Japan", "China", "Singapore", "Korea"]
series4 = pd.Series(s_dict, index =lst)
series4
print(series4.values)
print(series4.index)
print(series4.isnull())
print(series4.notnull())

与Dict区别二: index内值可以重复,尽管不推荐。

lst = ['A', 'B', 'B', 'C']
series5 = pd.Series(series1.values, index=lst)
series5
series5[['B', 'A']]

整个序列级别的元数据信息:name

当数据序列以及index本身有了名字,就可以更方便的进行后续的数据关联啦!

series4.name
series4.index.name
series4.name = "Capital Series"
series4.index.name = "Nation"
series4
pd.DataFrame(series4)

1.2 DataFrame:pandas的战锤(数据表,二维数组)

Series的有序集合,就像R的DataFrame一样方便。

仔细想想,绝大部分的数据形式都可以表现为DataFrame。

从Numpy二维数组、从文件或者从数据库定义:数据虽好,勿忘列名

data_np = np.asarray([('Japan', 'Tokyo', 4000),
('Korea', 'Seoul', 1300),
('China', 'Beijing', 9100)])
df1 = pd.DataFrame(data_np, columns=['nation','capital','GDP'])
df1

等长的列数据保存在一个字典里(JSON):很不幸,字典key是无序的

data_dict = {'nation': ['Japan', 'Korea', 'China'],
'capital': ['Tokyo', 'Seoul', 'Beijing'],
'GDP': [4900, 1300, 9100]}
df2 = pd.DataFrame(data_dict)
df2

从另一个DataFrame定义DataFrame:啊,强迫症犯了

df21 = pd.DataFrame(df2, columns=['nation', 'capital', 'GDP'])
df21
df22 = pd.DataFrame(df2, columns=['nation', 'capital', 'GDP'], index = [2, 0, 1])
df22

从DataFrame中取出列?两种方法(与JavaScript完全一致!)

  • '.'的写法容易与其他预留关键字产生冲突
  • '[ ]'的写法最安全。
print(df22.nation)
print(df22.capital)
print(df22['GDP'])
df22['capital']

从DataFrame中取出行?(至少)两种方法

df22[0:1] # 给出的实际是DataFrame
df22.iloc[0] # 通过对应Index给出行

像Numpy切片一样的终极招式:iloc

df22.iloc[0,:]
df22.iloc[:,0]

听说你从Table地狱来,大熊猫笑了

然而动态增加列无法用"."的方式完成,只能用"[ ]"

df22['population'] = [1600, 130, 55]
df22['region'] = 'East_Asian'
df22

1.3 Index:pandas进行数据操作的鬼牌(行级索引)

行级索引是

  • 元数据
  • 可能由真实数据产生,因此可以视作数据
  • 可以由多重索引也就是多个列组合而成
  • 可以和列名进行交换,也可以进行堆叠和展开,达到Excel透视表效果

Index有四种...哦不,很多种写法,一些重要的索引类型包括

  • pd.Index(普通)
  • Int64Index(数值型索引)
  • MultiIndex(多重索引,在数据操纵中更详细描述)
  • DatetimeIndex(以时间格式作为索引)
  • PeriodIndex (含周期的时间格式作为索引)

直接定义普通索引,长得就和普通的Series一样

index_names = ['a','b','c']
s = pd.Series(index_names)
print(pd.Index(index_names))
print(pd.Index(s))
s

Immutable,牢记

pd.Index是不可变的

index_names = ['a', 'b', 'c']
index0 = pd.Index(index_names)
print(index0.values)
# index0[2] = 'd' # 改变值会出错

扔进去一个含有多元组的List,就有了MultiIndex

MyltiIndex 也是immutable的。

multi1 = pd.Index([['Row_' + str(x + 1), 'Col_' + str(y + 1)] for x in range(4) for y in range(4)])
multi1.name = ('index1', 'index2')
multi1
multi2 = pd.Index([('Row_' + str(x + 1), 'Col_' + str(y + 1)) for x in range(4) for y in range(4)])
multi2
# multi2.name = ['index1', 'index2'] # 出错

对于Series来说,如果拥有了多重Index,数据,变形

下列代码说明:

  • 二重MultiIndex的Series可以unstack()成DataFrame
  • DataFrame可以stack成拥有二重MultiIndex的Series
data_for_multi1 = pd.Series(range(0, 16), index=multi2)
data_for_multi1
data_for_multi1.unstack()
data_for_multi1.unstack().stack()

我们来看一下非平衡数据的例子:

Row_1,2,3,4和Col_1,2,3,4并不是全组合的。

multi2 = pd.Index([('Row_' + str(x), 'Col_' + str(y + 1)) for x in range(5) for y in range(x)])
multi2
data_for_multi2 = pd.Series(np.arange(10), index=multi2)
data_for_multi2
data_for_multi2.unstack()
data_for_multi2.unstack().stack()

DateTime标准库如此好用,你值得拥有

import datetime
dates = [datetime.datetime(2021, 1, 1), datetime.datetime(2021, 1, 8), datetime.datetime(2021, 1, 30)]
pd.DatetimeIndex(dates)

如果你不仅需要时间格式统一,时间频率也要统一的话

periodindex1 = pd.period_range('2021-01', '2021-04', freq='M')
periodindex1

月级精度和日级精度如何转换?

有的公司统一以1号代表当月,有的公司统一以最后一天代表当月,转化起来很麻烦,可以asfreq

periodindex1.asfreq('D', how='start')
periodindex1.asfreq('D', how='end')

最后的最后,我要真正把两种频率的时间精度匹配上?

periodindex_mon = pd.period_range('2021-01', '2021-03', freq='M').asfreq('D', how='start')
periodindex_day = pd.period_range('2021-01-01', '2021-03-01', freq='D')

periodindex_mon
periodindex_day

粗粒度数据+reindex+ffill/bfill

full_ts = pd.Series(periodindex_mon, index=periodindex_mon).reindex(periodindex_day)
full_ts.head()
full_ts = pd.Series(periodindex_mon, index=periodindex_mon).reindex(periodindex_day, method='ffill')
full_ts.head()

关于索引,方便的操作有?

前面描述过了,索引有序,重复,但一定程度上又能通过key来访问,也就是说,某些集合操作都是可以支持的。

index1 = pd.Index(['A', 'B', 'B', 'C', 'C'])
index2 = pd.Index(['C', 'D', 'E', 'E', 'F'])
index3 = pd.Index(['B', 'C', 'A'])
print(index1.append(index2))
print(index1.difference(index2))
print(index1.intersection(index2))
print(index1.union(index2)) # Support unique-value Index well
print(index1.isin(index2))
print(index1.delete(2))
print(index1.insert(0, 'K')) # Not suggested
print(index3.drop('A')) # Support unique-value Index well
print(index1.is_monotonic, index2.is_monotonic, index3.is_monotonic)
print(index1.is_unique, index2.is_unique, index3.is_unique)

老生常谈,从基础来看,我们仍然关心pandas对于与外部数据是如何交互的。

2.1 结构化数据输入输出

  • read_csv与to_csv 是一对输入输出的工具,read_csv直接返回pandas.DataFrame,而to_csv只要执行命令即可写文件
    • read_table:功能类似
    • read_fwf:操作fixed width file
  • read_excel与to_excel方便的与excel交互

还记得刚开始的例子吗?

  • header 表示数据中是否存在列名,如果在第0行就写就写0,并且开始读数据时跳过相应的行数,不存在可以写none
  • names 表示要用给定的列名来作为最终的列名
  • encoding 表示数据集的字符编码,通常而言一份数据为了方便的进行文件传输都以utf-8作为标准

提问:下列例子中,header=4,names=cnames时,究竟会读到怎样的数据?

print('cnames:', cnames)
irisdata = pd.read_csv(iris_file, header=None, names=cnames, encoding='utf-8')
irisdata[::30]

希望了解全部参数的请移步API:

http://pandas.pydata.org/pandas-docs/stable/generated/pandas.read_csv.html#pandas.read_csv

这里介绍一些常用的参数:

读取处理:

  • skiprows:跳过一定的行数
  • nrows:仅读取一定的行数
  • skipfooter:尾部有固定的行数永不读取
  • skip_blank_lines:空行跳过

内容处理:

  • sep/delimiter:分隔符很重要,常见的有逗号,空格和Tab('\t')
  • na_values:指定应该被当作na_values的数值
  • thousands:处理数值类型时,每千位分隔符并不统一 (1.234.567,89或者1,234,567.89都可能),此时要把字符串转化为数字需要指明千位分隔符

收尾处理:

  • index_col:将真实的某列(列的数目,甚至列名)当作index
  • squeeze:仅读到一列时,不再保存为pandas.DataFrame而是pandas.Series

2.1.x Excel ... ?

对于存储着极为规整数据的Excel而言,其实是没必要一定用Excel来存,尽管Pandas也十分友好的提供了I/O接口。

iris_excel_file = 'irisdata.xls'
irisdata.to_excel(iris_excel_file, index=None)
irisdata_from_excel = pd.read_excel(iris_excel_file, header=0)
irisdata_from_excel[::30]
import os
os.remove(iris_excel_file)

唯一重要的参数:sheetname=k,标志着一个excel的第k个sheet页将会被取出。(从0开始)

2.2 半结构化数据

JSON:网络传输中常用的一种数据格式。

仔细看一下,实际上这就是我们平时收集到异源数据的风格是一致的:

  • 列名不能完全匹配
  • 关联键可能并不唯一
  • 元数据被保存在数据里
import json
json_data = [{'name': 'Wang', 'sal': 50000, 'job': 'VP'},
{'name': 'Zhang', 'job': 'Manager', 'report': 'VP'},
{'name': 'Li', 'sal': 5000, 'report': 'Manager'}]
data_employee = pd.read_json(json.dumps(json_data))
data_employee_ri = data_employee.reindex(columns=['name', 'job', 'sal', 'report'])
data_employee_ri

在第一部分的基础上,数据会有更多种操作方式:

  • 通过列名、行index来取数据,结合ix、iloc灵活的获取数据的一个子集(第一部分已经介绍)
  • 按记录拼接(就像Union All)或者关联(join)
  • 方便的自定义函数映射
  • 排序
  • 缺失值处理
  • 与Excel一样灵活的数据透视表(在第四部分更详细介绍)

3.1 数据整合:方便灵活

3.1.1 横向拼接:直接DataFrame

import numpy as np
import pandas as pd
pd.DataFrame([np.random.rand(2), np.random.rand(2), np.random.rand(2)], columns=['C1', 'C2'])

3.1.2 横向拼接:Concatenate

import json
json_data = [{'name': 'Wang', 'sal': 50000, 'job': 'VP'},
{'name': 'Zhang', 'job': 'Manager', 'report': 'VP'},
{'name': 'Li', 'sal': 5000, 'report': 'Manager'}]
data_employee = pd.read_json(json.dumps(json_data))
data_employee_ri = data_employee.reindex(columns=['name', 'job', 'sal', 'report'])

pd.concat([data_employee_ri, data_employee_ri, data_employee_ri])
pd.concat([data_employee_ri, data_employee_ri, data_employee_ri],ignore_index=True)

3.1.3 纵向拼接:Merge

根据数据列关联,使用on关键字

  • 可以指定一列或多列
  • 可以使用left_on和right_on
pd.merge(data_employee_ri, data_employee_ri, on='name')
pd.merge(data_employee_ri, data_employee_ri, on=['name', 'job'])

根据index关联,可以直接使用left_index和right_index

data_employee_ri.index.name = 'index1'
pd.merge(data_employee_ri, data_employee_ri, left_index=True, right_index=True)

TIPS: 增加how关键字,并指定

  • how = 'inner'
  • how = 'left'
  • how = 'right'
  • how = 'outer'

结合how,可以看到merge基本再现了SQL应有的功能,并保持代码整洁。

df31_a = pd.DataFrame({'name':['老王', '老张', '老李'], 'sal':[5000, 3000, 1000]})
df31_a
df31_b = pd.DataFrame({'name':['老王', '老刘'], 'job':['VP', 'Manager']})
df31_b

how='left': 保留左表信息

pd.merge(df31_a, df31_b, on='name', how='left')

how='right': 保留右表信息

pd.merge(df31_a, df31_b, on='name', how='right')

how='inner': 保留两表交集信息,这样尽量避免出现缺失值

pd.merge(df31_a, df31_b, on='name', how='inner')

how='outer': 保留两表并集信息,这样会导致缺失值,但最大程度的整合了已有信息

pd.merge(df31_a, df31_b, on='name', how='outer')

3.2 数据清洗三剑客

接下来的三个功能,map,applymap,apply,功能,是绝大多数数据分析师在数据清洗这一步骤中的必经之路。

他们分别回答了以下问题:

  • 我想根据一列数据新做一列数据,怎么办?(Series->Series)
  • 我想根据整张表的数据新做整张表,怎么办? (DataFrame->DataFrame)
  • 我想根据很多列的数据新做一列数据,怎么办? (DataFrame->Series)

不要再写什么for循环了!改变思维,提高编码和执行效率

data_np = np.asarray([('Japan', 'Tokyo', 4000),
('Korea', 'Seoul', 1300),
('China', 'Beijing', 9100)])
df32 = pd.DataFrame(data_np, columns=['nation', 'capital', 'GDP'])
df32

map: 以相同规则将一列数据作一个映射,也就是进行相同函数的处理

def GDP_Factorize(v):
fv = np.float64(v)
if fv > 6000.0:
return 'High'
elif fv < 2000.0:
return 'Low'
else:
return 'Medium'

df32['GDP_Level'] = df32['GDP'].map(GDP_Factorize)
df32['NATION'] = df32.nation.map(str.upper)
df32

类似的功能还有applymap,可以对一个dataframe里面每一个元素像map那样全局操作

df32.applymap(lambda x: float(x)*2 if x.isdigit() else x.upper())

apply则可以对一个DataFrame操作得到一个Series

他会有点像我们后面介绍的agg,但是apply可以按行操作和按列操作,用axis控制即可。

df32.apply(lambda x: x['nation'] + x['capital'] + '_' + x['GDP'], axis=1)

3.3 数据排序

  • sort: 按一列或者多列的值进行行级排序
  • sort_index: 根据index里的取值进行排序,而且可以根据axis决定是重排行还是列
data_np = np.asarray([('Japan', 'Tokyo', 4000),
('Korea', 'Seoul', 1300),
('China', 'Beijing', 9100)])
df33 = pd.DataFrame(data_np, columns=['nation', 'capital', 'GDP'])
df33
df33.sort_values(['capital', 'nation'])
df33.sort_values('GDP', ascending=False)
df33.sort_index(axis=1, ascending=True)

一个好用的功能:Rank

df33
df33.rank()
df33.rank(ascending=False)

注意tied data(相同值)的处理:

  • method = 'average'
  • method = 'min'
  • method = 'max'
  • method = 'first'
df33x = pd.DataFrame({'name': ['老王', '老张', '老李', '老刘'],
'sal': np.array([5000, 3000, 5000, 9000])})
df33x

df33x.rank()默认使用method='average',两条数据相等时,处理排名时大家都用平均值

df33x.sal.rank()

method='min',处理排名时大家都用最小值

df33x.sal.rank(method='min')

method='max',处理排名时大家都用最大值

df33x.sal.rank(method='max')

method='first',处理排名时谁先出现就先给谁较小的数值。

df33x.sal.rank(method='first')

3.4 缺失数据处理

i = pd.Index([('Row_' + str(x), 'Col_' + str(y + 1)) for x in range(5) for y in range(x)])
data_multi = pd.Series(np.arange(10), index=i)
df34 = data_multi.unstack()
df34

忽略缺失值:

df34.mean(skipna=True)
df34.mean(skipna=False)

如果不想忽略缺失值的话,就需要祭出fillna了:

df34
df34.fillna(0).mean(axis=1, skipna=False)

Pandas的groupby

groupby的功能类似SQL的group by关键字:

Split-Apply-Combine

  • Split,就是按照规则分组
  • Apply,通过一定的agg函数来获得输入pd.Series返回一个值的效果
  • Combine,把结果收集起来

Pandas的groupby的灵活性:

  • 分组的关键字可以来自于index,也可以来自于真实的列数据
  • 分组规则可以通过一列或者多列

分组的具体逻辑

iris_file = '../data/numpy/iris.data.txt'
cnames = ['sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'class']

irisdata = pd.read_csv(iris_file, names=cnames, encoding='utf-8')
irisdata
irisdata_group = irisdata.groupby('class')
irisdata_group
for level, subsetDF in irisdata_group:
print(level)
print(subsetDF[::20])

分组可以快速实现MapReduce的逻辑

  • Map: 指定分组的列标签,不同的值就会被扔到不同的分组处理
  • Reduce: 输入多个值,返回一个值,一般可以通过agg实现,agg能接受一个函数
irisdata.groupby('class').agg(lambda x:((x - x.mean())**3).sum() * (len(x) - 0.0) / 
(len(x) - 1.0) / (len(x) - 2.0) / (x.std() * np.sqrt((len(x) - 0.0) /
(len(x)-1.0)))**3 if len(x) > 2 else None)
import scipy.stats
irisdata.groupby('class').agg(scipy.stats.skew)

汇总之后的广播操作

在OLAP数据库上,为了避免groupby+join的二次操作,提出了sum()over(partition by)的开窗操作。

在Pandas中,这种操作能够进一步被transform所取代。

pd.concat([irisdata, irisdata.groupby('class').transform('mean')], axis=1)[::20]

产生 MultiIndex(多列分组)后的数据透视表操作

一般来说,多列groupby的一个副作用就是.groupby().agg()之后你的行index已经变成了一个多列分组的分级索引。

如果我们希望达到Excel的数据透视表的效果,行和列的索引自由交换,达到统计目的,究竟应该怎么办呢?

factor1 = np.random.randint(0, 3, 50)
factor2 = np.random.randint(0, 2, 50)
factor3 = np.random.randint(0, 3, 50)
values = np.random.randn(50)
hierindexDF = pd.DataFrame({'F1': factor1, 'F2': factor2, 'F3': factor3, 'F4': values})
hierindexDF.tail()
hierindexDF_gbsum = hierindexDF.groupby(['F1', 'F2', 'F3']).sum()
hierindexDF_gbsum

观察Index:

hierindexDF_gbsum.index

unstack:

  • 无参数时,把最末index置换到column上
  • 有数字参数时,把指定位置的index置换到column上
  • 有列表参数时,依次把特定位置的index置换到column上
hierindexDF_gbsum.unstack()
hierindexDF_gbsum.unstack(0)
hierindexDF_gbsum.unstack(1)
hierindexDF_gbsum.unstack([2,0])

更进一步的,stack的功能是和unstack对应,把column上的多级索引换到index上去

hierindexDF_gbsum.unstack([2, 0]).stack([1, 2])