注意:本文是根据b站视频总结的笔记,原视频在这里
matplotlib部分
matplotlib主要用于画图。
1. 先从一个简单的代码开始
我们收集了某城市某天内每隔2小时的气温值,准备画出一天气温变化图。
from matplotlib import pyplot as plt
x = range(2, 26, 2) # x轴数据
y = [15, 13, 14.5, 17, 20, 25, 26, 26, 24, 22, 18, 15] # y轴数据
plt.plot(x, y) # 传入x和y,绘制折线图
plt.show() # 展示图形
运行结果如下图:
可以发现图已经画出来了,但是有时候这并不是我们想要的成品图,我们希望能对他进行一些个性化的操作。
2. 对图片进行一些调整
(1)设置图形大小
plt.figure(figsize=(20, 8), dpi=80)
如果觉得图片太小了,可以用上面的代码,设置长宽和dpi。
(2)调整坐标轴刻度
plt.xticks(ticks, labels)
常见用法如上。第一个参数ticks
传递一个数组,通常直接传递x
即可。第二个参数labels
也是一个数组,且长度与x
一般相同,用于对ticks
中对应元素进行替换。例如在上面简单的代码中,加入如下内容:
from matplotlib import pyplot as plt
x = range(2, 26, 2)
y = [15, 13, 14.5, 17, 20, 25, 26, 26, 24, 22, 18, 15]
plt.figure(figsize=(12, 8), dpi=80)
plt.xticks(x) # 只传入第一个参数
plt.plot(x, y)
plt.show()
显示结果如下:
可以看到x轴的坐标已经按照x的值显示出来了。但是显示的只是数字,如果能显示“2点”,“4点”这样,会更加直观,这就需要用到第二个参数。将代码改成下面这样:
from matplotlib import pyplot as plt
x = range(2, 26, 2)
y = [15, 13, 14.5, 17, 20, 25, 26, 26, 24, 22, 18, 15]
x_ticks = ["{} o'clock".format(i) for i in x]
plt.figure(figsize=(12, 8), dpi=80)
plt.xticks(x, x_ticks) # 传入两个参数,将x轴的值和字符对应起来
plt.plot(x, y)
plt.show()
显示结果如下:
这样一来,x轴的效果就很好了。但是y轴还是可以再改一下。由于我们没有设置y轴,因此系统默认为我们生成了一个。但是由于间隔为2,我们现在并不能很好的读取每一个点的值,希望能把y轴间隔弄小一点。同理,使用plt.yticks
即可。
from matplotlib import pyplot as plt
x = range(2, 26, 2)
y = [15, 13, 14.5, 17, 20, 25, 26, 26, 24, 22, 18, 15]
x_ticks = ["{} o'clock".format(i) for i in x]
plt.figure(figsize=(12, 8))
plt.xticks(x, x_ticks)
plt.yticks(range(min(y), max(y) + 1)) # 根据y的值域显示y轴刻度
plt.plot(x, y)
plt.show()
显示效果如下:
(3)增加网格
为了更加清楚的展示每个点的位置,我们可以为图形添加网格:
plt.grid()
添加网格后效果如下:
(4)添加图形的描述并显示中文
接下来我们为图形增加坐标轴描述和标题:
plt.xlabel("时间")
plt.ylabel("温度:摄氏度")
plt.title("某地一天气温变化折线图")
如果直接增加这些代码,运行程序,会发现图虽然能显示出来,但是所有的汉字都变成了框框,而且python还报了一堆错。这主要是由于默认字体中不含中文字符造成的。我们可以手动修改字体,显示中文。
方法一:全局设置
import matplotlib
matplotlib.rc("font", family='Source Han Sans CN', weight="regular", size="12")
方法二:分内容设置
from matplotlib import font_manager
my_font = font_manager.FontProperties(fname="/usr/share/fonts/adobe-source-han-sans/SourceHanSansCN-Regular.otf")
plt.xlabel("时间", fontproperties=my_font)
plt.ylabel("温度:摄氏度", fontproperties=my_font)
plt.title("某地一天气温变化折线图", fontproperties=my_font)
我们采用全局设置,并将标题加粗,字号调大,修改完的代码如下:
from matplotlib import pyplot as plt
import matplotlib
matplotlib.rc("font", family='Source Han Sans CN', weight="regular", size="12")
x = range(2, 26, 2)
y = [15, 13, 14.5, 17, 20, 25, 26, 26, 24, 22, 18, 15]
x_ticks = ["{}点".format(i) for i in x]
plt.figure(figsize=(12, 8))
plt.grid()
plt.xticks(x, x_ticks)
plt.yticks(range(min(y), max(y) + 1))
plt.xlabel("时间")
plt.ylabel("温度:摄氏度")
plt.title("某地一天气温变化折线图", weight="bold", size="16")
plt.plot(x, y)
plt.show()
最后效果如图:
(5)保存图形
到这里基本的效果已经有了,除了直接输出图片,还可以保存图片:
plt.savefig(path)
这里可以保存为png格式,也可以保存为svg格式,按需求来就行。注意,plt.savefig()
必须在画图之后在能保存,即应放在plt.plot()
的后面。
3. 常见的图形的画法
(1)折线图
plt.plot(x, y, label, color, linestyle, ...)
案例:
画出两个人从11岁至30岁每年交女朋友的数量变化图
a = [1,0,1,1,2,4,3,2,3,4,4,5,6,5,4,3,3,1,1,1]
b = [1,0,3,1,2,2,3,3,2,1 ,2,1,1,1,1,1,1,1,1,1]
分析:
在同一幅图中画两条折线,只需要使用plot
方法两次即可,同时为每一条线设置标签。
代码:
from matplotlib import pyplot as plt
import matplotlib
matplotlib.rc("font", family='Source Han Sans CN', weight="regular", size="12")
x = range(11, 31)
y1 = [1, 0, 1, 1, 2, 4, 3, 2, 3, 4, 4, 5, 6, 5, 4, 3, 3, 1, 1, 1]
y2 = [1, 0, 3, 1, 2, 2, 3, 3, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1]
x_ticks = ["{}岁".format(i) for i in x]
plt.figure(figsize=(12, 8))
plt.grid(alpha=0.4) # 设置网格透明度
plt.xticks(x, x_ticks)
plt.xlabel("年龄")
plt.ylabel("女朋友数量")
plt.title("A和B从11岁至30岁女朋友数量变化图", weight="bold", size="16")
plt.plot(x, y1, label="A同学", color="#F08080")
plt.plot(x, y2, label="B同学", color="#DB7093", linestyle="--")
plt.legend() # 显示图例
plt.show()
效果:
(2)散点图
plt.scatter(x, y, label, ...)
案例:
画出三月和十月每天气温的变化散点图
y3 = [11,17,16,11,12,11,12,6,6,7,8,9,12,15,14,17,18,21,16,17,20,14,15,15,15,19,21,22,22,22,23]
y10 = [26,26,28,19,21,17,16,19,18,20,20,19,22,23,17,20,21,20,22,15,11,15,5,13,17,10,11,13,12,13,6]
分析:
通过控制x将两个散点图画在同一张图里,便于比较
代码:
from matplotlib import pyplot as plt
import matplotlib
matplotlib.rc("font", family='Source Han Sans CN', weight="regular", size="12")
x3 = range(1, 32)
y3 = [
11, 17, 16, 11, 12, 11, 12, 6, 6, 7, 8, 9, 12, 15, 14, 17, 18, 21, 16, 17,
20, 14, 15, 15, 15, 19, 21, 22, 22, 22, 23
]
x10 = range(41, 72)
y10 = [
26, 26, 28, 19, 21, 17, 16, 19, 18, 20, 20, 19, 22, 23, 17, 20, 21, 20, 22,
15, 11, 15, 5, 13, 17, 10, 11, 13, 12, 13, 6
]
x = list(x3) + list(x10)
x_ticks = ["3月{}日".format(i) for i in x3] + ["10月{}日".format(i - 40) for i in x10]
plt.figure(figsize=(15, 10))
plt.xticks(x[::3], x_ticks[::3], rotation=45)
plt.xlabel("日期")
plt.ylabel("温度:摄氏度")
plt.title("三月和十月每天气温的变化散点图", weight="bold", size="16")
plt.scatter(x3, y3, label="三月", color="orange")
plt.scatter(x10, y10, label="十月", color="cyan")
plt.legend()
plt.show()
这里有两个点值得注意。第一个是通过控制x的范围将两个散点图在同一张图上分开显示,第二个就是对于x轴刻度的控制。因为如果31天全都写出来,会显得非常挤,所以在用plt.xticks
做对应的时候,每隔3天对应一次,这样中间未对应的值就不会显示了,同时还将文本旋转45度便于查看。
效果:
(3)条形图
plt.bar(x, y, width) # 纵向条形图
plt.barh(x, y, height) # 横向条形图
I. 纵向条形图
案例:
画出2017年电影票房的条形图
a = ["战狼2","速度与激情8","功夫瑜伽","西游伏妖篇","变形金刚5:最后的骑士","摔跤吧!爸爸","加勒比海盗5:死无对证","金刚:骷髅岛","极限特工:终极回归","生化危机6:终章","乘风破浪","神偷奶爸3","智取威虎山","大闹天竺","金刚狼3:殊死一战","蜘蛛侠:英雄归来","悟空传","银河护卫队2","情圣","新木乃伊"]
b = [56.01,26.94,17.53,16.49,15.45,12.96,11.8,11.61,11.28,11.12,10.49,10.3,8.75,7.55,7.32,6.99,6.88,6.86,6.58,6.23]
代码:
from matplotlib import pyplot as plt
import matplotlib
matplotlib.rc("font", family='Source Han Sans CN', weight="regular", size="10")
x_ticks = [
"战狼2", "速度与激情8", "功夫瑜伽", "西游伏妖篇", "变形金刚5:最后的骑士", "摔跤吧!爸爸", "加勒比海盗5:死无对证",
"金刚:骷髅岛", "极限特工:终极回归", "生化危机6:终章", "乘风破浪", "神偷奶爸3", "智取威虎山", "大闹天竺",
"金刚狼3:殊死一战", "蜘蛛侠:英雄归来", "悟空传", "银河护卫队2", "情圣", "新木乃伊"
]
x = range(len(x_ticks))
y = [
56.01, 26.94, 17.53, 16.49, 15.45, 12.96, 11.8, 11.61, 11.28, 11.12, 10.49,
10.3, 8.75, 7.55, 7.32, 6.99, 6.88, 6.86, 6.58, 6.23
]
plt.figure(figsize=(15, 10))
plt.xticks(x, x_ticks, rotation=45)
plt.xlabel("电影名称")
plt.ylabel("票房:亿元")
plt.title("2017年电影票房条形图", weight="bold", size="16")
plt.bar(x, y, 0.5)
plt.show()
效果:
我们发现由于名称太长,即便旋转之后仍然难以阅读,故考虑使用横向条形图。
II. 横向条形图
代码:
from matplotlib import pyplot as plt
import matplotlib
matplotlib.rc("font", family='Source Han Sans CN', weight="regular", size="10")
y_ticks = [
"战狼2", "速度与激情8", "功夫瑜伽", "西游伏妖篇", "变形金刚5:最后的骑士", "摔跤吧!爸爸", "加勒比海盗5:死无对证",
"金刚:骷髅岛", "极限特工:终极回归", "生化危机6:终章", "乘风破浪", "神偷奶爸3", "智取威虎山", "大闹天竺",
"金刚狼3:殊死一战", "蜘蛛侠:英雄归来", "悟空传", "银河护卫队2", "情圣", "新木乃伊"
]
y = range(len(y_ticks))
x = [
56.01, 26.94, 17.53, 16.49, 15.45, 12.96, 11.8, 11.61, 11.28, 11.12, 10.49,
10.3, 8.75, 7.55, 7.32, 6.99, 6.88, 6.86, 6.58, 6.23
]
plt.figure(figsize=(15, 10))
plt.yticks(y, y_ticks)
plt.xlabel("票房:亿元")
plt.ylabel("电影名称")
plt.title("2017年电影票房条形图", weight="bold", size="16")
plt.barh(y, x, 0.5)
plt.show()
效果:
我们发现在使用横向条形图的效果比纵向要好。不过要注意的是,在绘制横向条形图时,虽然图上x轴和y轴的相对位置没有变化,但是plt.barh()
方法是先传y,再传x的。
III. 多个条形图
案例:
展示几部电影连续几天票房的对比情况
a = ["猩球崛起3:终极之战","敦刻尔克","蜘蛛侠:英雄归来","战狼2"]
b1 = [2358,399,2358,362]
b2 = [12357,156,2045,168]
b3 = [15746,312,4497,319]
分析:
我们同样可以通过改变x轴的位置让同一幅图中显示多个条形图
代码:
from matplotlib import pyplot as plt
import matplotlib
matplotlib.rc("font", family='Source Han Sans CN', weight="regular", size="10")
x_ticks = ["猩球崛起3:终极之战", "敦刻尔克", "蜘蛛侠:英雄归来", "战狼2"]
bar_width = 0.2
x1 = range(len(x_ticks))
x2 = [i + bar_width for i in x1]
x3 = [i + bar_width * 2 for i in x1]
x = list(x1) + x2 + x3
b1 = [2358, 399, 2358, 362]
b2 = [12357, 156, 2045, 168]
b3 = [15746, 312, 4497, 319]
plt.figure(figsize=(15, 10))
plt.xticks(x2, x_ticks)
plt.xlabel("电影名称")
plt.ylabel("票房:亿元")
plt.title("几部电影连续几天票房的对比情况", weight="bold", size="16")
plt.bar(x1, b1, bar_width, label="第一天")
plt.bar(x2, b2, bar_width, label="第二天")
plt.bar(x3, b3, bar_width, label="第三天")
plt.legend()
plt.show()
代码这里还有地方值得分析一下。为什么使用x1,x2,x3就可以做到这样的效果?在本代码中,x1 = [0, 1, 2, 3]
,x2 = [0.2, 1.2, 2.2, 3.2]
,x3 = [0.4, 1.4, 2.4, 3.4]
,由于每一条的宽度统一设置为0.2,意味着第一条的中点在0,在[-0.1, 0.1]的范围,第二条是以0.2为中点,在[0.1, 0.3]的范围内,第三条是以0.4为中点,在[0.3, 0.5]的范围内,这样我们就发现每一组的三条是紧挨在一起的。
第二个问题就是,三个plt.bar()
代码分别画的是哪一部分呢?这个应该好理解,三条命令分别画的是蓝色、橙色和绿色,也就是说紧挨在一起的三条并不是同一条代码画出来的,而是先画每一组的第一条,再第二条,最后第三条。
效果:
(4)直方图
plt.hist(x, bins) # 频数直方图
plt.hist(x, bins, density=True) # 频率直方图
案例:
画出对250部电影时长分析的直方图
a = [131, 98, 125, 131, 124, 139, 131, 117, 128, 108, 135, 138, 131, 102, 107, 114, 119, 128, 121, 142, 127, 130, 124, 101, 110, 116, 117, 110, 128, 128, 115, 99, 136, 126, 134, 95, 138, 117, 111,78, 132, 124, 113, 150, 110, 117, 86, 95, 144, 105, 126, 130,126, 130, 126, 116, 123, 106, 112, 138, 123, 86, 101, 99, 136,123, 117, 119, 105, 137, 123, 128, 125, 104, 109, 134, 125, 127,105, 120, 107, 129, 116, 108, 132, 103, 136, 118, 102, 120, 114,105, 115, 132, 145, 119, 121, 112, 139, 125, 138, 109, 132, 134,156, 106, 117, 127, 144, 139, 139, 119, 140, 83, 110, 102,123,107, 143, 115, 136, 118, 139, 123, 112, 118, 125, 109, 119, 133,112, 114, 122, 109, 106, 123, 116, 131, 127, 115, 118, 112, 135,115, 146, 137, 116, 103, 144, 83, 123, 111, 110, 111, 100, 154,136, 100, 118, 119, 133, 134, 106, 129, 126, 110, 111, 109, 141,120, 117, 106, 149, 122, 122, 110, 118, 127, 121, 114, 125, 126,114, 140, 103, 130, 141, 117, 106, 114, 121, 114, 133, 137, 92,121, 112, 146, 97, 137, 105, 98, 117, 112, 81, 97, 139, 113,134, 106, 144, 110, 137, 137, 111, 104, 117, 100, 111, 101, 110,105, 129, 137, 112, 120, 113, 133, 112, 83, 94, 146, 133, 101,131, 116, 111, 84, 137, 115, 122, 106, 144, 109, 123, 116, 111,111, 133, 150]
分析:
直方图中涉及到一个组数的概念,组数 = 极差/组距,且最好是整除得到的组数。
代码:
from matplotlib import pyplot as plt
import matplotlib
matplotlib.rc("font", family='Source Han Sans CN', weight="regular", size="10")
a = [
131, 98, 125, 131, 124, 139, 131, 117, 128, 108, 135, 138, 131, 102, 107,
114, 119, 128, 121, 142, 127, 130, 124, 101, 110, 116, 117, 110, 128, 128,
115, 99, 136, 126, 134, 95, 138, 117, 111, 78, 132, 124, 113, 150, 110,
117, 86, 95, 144, 105, 126, 130, 126, 130, 126, 116, 123, 106, 112, 138,
123, 86, 101, 99, 136, 123, 117, 119, 105, 137, 123, 128, 125, 104, 109,
134, 125, 127, 105, 120, 107, 129, 116, 108, 132, 103, 136, 118, 102, 120,
114, 105, 115, 132, 145, 119, 121, 112, 139, 125, 138, 109, 132, 134, 156,
106, 117, 127, 144, 139, 139, 119, 140, 83, 110, 102, 123, 107, 143, 115,
136, 118, 139, 123, 112, 118, 125, 109, 119, 133, 112, 114, 122, 109, 106,
123, 116, 131, 127, 115, 118, 112, 135, 115, 146, 137, 116, 103, 144, 83,
123, 111, 110, 111, 100, 154, 136, 100, 118, 119, 133, 134, 106, 129, 126,
110, 111, 109, 141, 120, 117, 106, 149, 122, 122, 110, 118, 127, 121, 114,
125, 126, 114, 140, 103, 130, 141, 117, 106, 114, 121, 114, 133, 137, 92,
121, 112, 146, 97, 137, 105, 98, 117, 112, 81, 97, 139, 113, 134, 106, 144,
110, 137, 137, 111, 104, 117, 100, 111, 101, 110, 105, 129, 137, 112, 120,
113, 133, 112, 83, 94, 146, 133, 101, 131, 116, 111, 84, 137, 115, 122,
106, 144, 109, 123, 116, 111, 111, 133, 150
]
d = 6 # 组距
bins = (max(a) - min(a)) // d # 组数
plt.figure(figsize=(15, 10))
plt.grid()
plt.xlabel("时长")
plt.ylabel("数量")
plt.title("250部电影时长分析的直方图", weight="bold", size="16")
plt.xticks(range(min(a), max(a) + d, d))
plt.hist(a, bins) # 频数直方图
plt.show()
效果:
(5)其他
更多图表见官网:https://matplotlib.org/gallery/index.html
numpy部分
numpy主要用于处理数值型数据。
(1)使用numpy生成数组
import numpy as np
a = np.array([1, 2, 3, 4, 5])
b = np.array(range[1, 6])
c = np.arange((1, 6))
上面的a
,b
,c
的值是一样的,都是一个类型为numpy.ndarray
的数组[1 2 3 4 5]
。
(2)数组的形状
import numpy as np
t1 = np.arange(12)
t2 = np.array([[1, 2, 3], [4, 5, 6]])
>>> t1
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
>>> t2
array([[1, 2, 3],
[4, 5, 6]])
查看数组的形状:
>>> t1.shape
(12,)
>>> t2.shape
(2, 3)
shape
属性返回的是一个元组,说明了当前数组的形状。可以看出:
- 元组的元素个数表示了数组的维数
- 元组的第一个元素表示第一层数组所含元素个数,第二个元素表示第二层数组所含元素个数,以此类推
- 当数组为二维时,第一个元素即为行数,第二个元素即为列数
修改数组的形状
>>> t1.reshape((3, 4)) # 将t1变成一个3行4列的数组
array([[0, 1, 2, 3],
[4, 5, 6, 7],
[8, 9, 10, 11]])
>>> t1.reshape((3, 5)) # 试图将t1变成一个3行5列的数组
Traceback (most recent call last):
File "", line 1, in
ValueError: cannot reshape array of size 12 into shape (3,5)
可以看出,reshape
方法传递一个表示维数的元组,就会返回一个按要求修改的新数组,而不改变原数组。如果无法修改,则会报错。
除了将低维数组变成高维,还可以降维:
>>> t2.reshape((6,))
array([1, 2, 3, 4, 5, 6])
>>> t2.flatten()
array([1, 2, 3, 4, 5, 6])
降维除了使用reshape
,如果直接降成一维,可以使用flatten
方法。
(3)数组的计算
数组与数字计算:
与数字进行计算时,会把计算应用到数组中的每一个元素,这被称为“广播机制”。
数组与同维度数组计算:
与同维度数组进行计算时,会让每一个对应的元素进行计算。
数组与不同维度的数组进行计算:
>>> t3 = np.arange(12).reshape((3, 4))
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
>>> t4 = np.arange(3).reshape((3, 1))
array([[0],
[1],
[2]])
S
>>> t5 = np.arange(4)
array([0, 1, 2, 3])
>>> t3 + t4
array([[ 0, 1, 2, 3],
[ 5, 6, 7, 8],
[10, 11, 12, 13]])
>>> t3 + t5
array([[ 0, 2, 4, 6],
[ 4, 6, 8, 10],
[ 8, 10, 12, 14]])
可以看到,尽管两个数组的维数不同,但如果在横向上维度相同,则在横向上进行运算,如果在竖向上维度相同,则在竖向上进行运算。
事实上广播原则并不局限于与数字计算时。如果两个不同维度的数组没有1维的方面,两个数组仍然是可以计算的。例如:a = (3,3,2)可以和b = (3,2)进行运算,而不能和c = (3,3)进行运算。因为a中有三个(3,2)的数组,可以使用广播机制,而a中没有(3,3)的数组,无法运算。
(4)numpy读取文件
数据分析中,一般使用的是csv文件,即逗号分隔值文件。
np.loadtxt(frame, dtype=np.float, delimiter=None, skiprows=0, usecols=None, unpack=False)
参数 | 解释 |
---|---|
frame | 文件、字符串或产生器,可以是gz或bz2压缩文件 |
dtype | 数据类型,可选,默认为np.float |
delimiter | 分隔字符串,默认是任何空格,若读取csv文件则改为逗号 |
skiprows | 跳过前x行 |
usecols | 读取指定的列,传入列索引的元组 |
unpack | 如果为True,则对源文件数据转置,如果为False,则保持原文件数据不变 |
(5)numpy中的转置
t.transpose()
t.T
t.swapaxis(1, 0)
以上三种方法都可以实现二位数组t的转置。
(6)numpy的索引和切片
# 取行
t[2]
# 取连续的多行
t[2:5]
# 取不连续的多行
t[[2, 8, 10]]
# 取列
t[:, 2]
# 取连续的多列
t[:, 2:5]
# 取不连续的多列
t[:, [2, 8, 10]]
# 同时取行和列
t[3, 4]
# 取多行和多列(3-5行,2-4列)
t[2:5, 1:4]
# 取多个不相邻的点
t[[0, 2, 5], [2, 4, 8]]
(7)numpy中的布尔索引
>>> t = np.arange(24).reshape((4, 6))
>>> t < 10
array([[ True, True, True, True, True, True],
[ True, True, True, True, False, False],
[False, False, False, False, False, False],
[False, False, False, False, False, False]])
>>> t[t < 10] = 3
array([[ 3, 3, 3, 3, 3, 3],
[ 3, 3, 3, 3, 10, 11],
[12, 13, 14, 15, 16, 17],
[18, 19, 20, 21, 22, 23]])
>>> t[t > 20]
array([21, 22, 23])
还可以通过where
和clip
三元运算符进行修改。
>>> t
array([[ 3, 3, 3, 3, 3, 3],
[ 3, 3, 3, 3, 10, 11],
[12, 13, 14, 15, 16, 17],
[18, 19, 20, 21, 22, 23]])
>>> np.where(t<=3, 0, 100) # t小于等于3的元素替换为0,否则替换为100
array([[ 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 100, 100],
[100, 100, 100, 100, 100, 100],
[100, 100, 100, 100, 100, 100]])
>>> t.clip(10, 18) # t中小于10的替换为10,大于18的替换为18
array([[10, 10, 10, 10, 10, 10],
[10, 10, 10, 10, 10, 11],
[12, 13, 14, 15, 16, 17],
[18, 18, 18, 18, 18, 18]])
值得注意的是where
和clip
都会返回一个新数组,而不修改原来的数组。
(8)数组的拼接
>>> t1 = np.arange(12).reshape((2, 6))
array([[ 0, 1, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10, 11]])
>>> t2 = np.arange(12, 24).reshape((2, 6))
array([[12, 13, 14, 15, 16, 17],
[18, 19, 20, 21, 22, 23]])
>>> np.vstack((t1, t2)) # 竖直拼接
array([[ 0, 1, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10, 11],
[12, 13, 14, 15, 16, 17],
[18, 19, 20, 21, 22, 23]])
>>> np.hstack((t1, t2)) # 水平拼接
array([[ 0, 1, 2, 3, 4, 5, 12, 13, 14, 15, 16, 17],
[ 6, 7, 8, 9, 10, 11, 18, 19, 20, 21, 22, 23]])
(9)数组的行列交换
>>> t = np.arange(12, 24).reshape((3, 4))
array([[12, 13, 14, 15],
[16, 17, 18, 19],
[20, 21, 22, 23]])
>>> t[[1, 2],:] = t[[2, 1],:] # 行交换
array([[12, 13, 14, 15],
[20, 21, 22, 23],
[16, 17, 18, 19]])
>>> t[:, [0, 2]] = t[:, [2, 0]] # 列交换
array([[14, 13, 12, 15],
[22, 21, 20, 23],
[18, 17, 16, 19]])
(10)numpy中的轴
轴(axis)使用0,1,2,……等数字进行表示。在一个形状为(2,)的一维数组中,只有0轴,长度为2;在形状为(2,3)的二维数组中,有0轴和1轴,0轴长度为2,1轴长度为3,等等。
(11)numpy中的nan和inf
nan(Not a Number):不是一个数字。
在以下情况可能出现nan:
- 读取本地文件为float格式,如果有缺失,则显示nan
- 做了不合适的运算,例如0 / 0 或inf - inf
关于nan,需要注意的有:
-
type(np.nan)
为float类型 -
np.nan == np.nan
返回False,np.nan != np.nan
返回True - nan和任何值计算都是nan
- 可以使用
np.isnan(t)
判断一个数是不是nan或一个数组中是否含有nan - 可以使用
np.count_nonzero(t!=t)
或np.count_nonzero(np.isnan(t))
计算数组中nan的个数
inf(infinity):无穷大,分为+inf和-inf。
在以下情况可能出现inf:
- 一个数字除以0
nan和inf都是float类型
(12)numpy中的常用统计函数
方法 | 说明 |
---|---|
t.sum(axis=None) | 求和 |
t.mean(axis=None) | 平均值 |
np.median(t, axis=None) | 中位数 |
t.max(axis=None) | 最大值 |
t.mim(axis=None) | 最小值 |
np.ptp(t, axis=None) | 极差 |
t.std(axis=None) | 标准差 |
(13)其他方法
方法 | 说明 |
---|---|
t.astype(int) | 修改数组t的数据类型 |
np.argmax(t, axis=0) | 获取数组t在0轴上的最大值的位置 |
np.argmin(t, axis=1) | 获取数组t在1轴上的最小值的位置 |
np.zeros((3, 4)) | 创建一个全0的数组 |
np.ones((3, 4)) | 创建一个全1的数组 |
np.eye(3) | 创建一个三阶单位矩阵 |
pandas部分
pandas主要用于处理字符串数据。
pandas的常用数据类型:
- Series:一维,带标签数组
- DataFrame:二维,Series容器
(1)创建Series
import pandas as pd
import numpy as np
# 通过列表创建Series
t1 = pd.Series([1, 2, 3, 4, 5], index=list("abcde"))
# 通过字典创建Series
t2 = pd.Series({"name": "zhangsan", "age": 18, "tel": 10086})
# 通过numpy数组创建Series
t3 = pd.Series(np.arange(5))
如果不指定index,则默认是0,1,2,…。如果是字典类型创建的,则index为字典的key。
>>> t2
name zhangsan
age 18
tel 10086
dtype: object
>>> t3
0 0
1 1
2 2
3 3
4 4
dtype: int64
>>> type(t2)
>>> 0 0.0
1 1.0
2 2.0
3 3.0
4 4.0
dtype: float64
Series有两个属性,分别是index
和values
,可以获取到一个Series的索引和值。
>>> t2.index
Index(['name', 'age', 'tel'], dtype='object')
>>> t2.values
array(['zhangsan', 18, 10086], dtype=object)
(2)Series的切片和索引
>>> t2["age"]
18
>>> t2[0]
'zhangsan'
>>> t2[1:]
age 18
tel 10086
dtype: object
>>> t2[[0, 2]]
name zhangsan
tel 10086
dtype: object
>>> t3[t3>=3]
3 3
4 4
dtype: int64
(3)创建DataFrame
>>> t1 = pd.DataFrame(np.arange(12).reshape((3, 4)))
>>> t1
0 1 2 3
0 0 1 2 3
1 4 5 6 7
2 8 9 10 11
>>> t1 = pd.DataFrame(np.arange(12).reshape((3, 4)), index=list("abc"), columns=list("WXYZ"))
>>> t1
W X Y Z
a 0 1 2 3
b 4 5 6 7
c 8 9 10 11
>>> t2 = pd.DataFrame({"name": ["zhangsan", "lisi"], "age": [18, 22], "tel": [10086, 10000]})
>>> t2
name age tel
0 zhangsan 18 10086
1 lisi 22 10000
>>> t3 = pd.DataFrame([{"name": "zhangsan", "age": 18, "tel": 10086}, {"name": "lisi", "age": 18}])
>>> t3
name age tel
0 zhangsan 18 10086.0
1 lisi 18 NaN
可以看到3*4的矩阵外面多出来一列和一行,竖着的一列叫index,即行索引,axis=0;横着的一行叫columns,即列索引,axis=1。
创建DataFrame时,可以传入一个每个元素是列表的字典,也可以传入一个每个元素是字典的列表,两者相同。且没有对应索引的值处显示NaN。
(4)DataFrame的描述信息
>>> t3.index
RangeIndex(start=0, stop=2, step=1)
>>> t3.columns
Index(['name', 'age', 'tel'], dtype='object')
>>> t3.values
array([['zhangsan', 18, 10086.0],
['lisi', 18, nan]], dtype=object)
>>> t3.shape
(2, 3)
>>> t3.dtypes
name object
age int64
tel float64
dtype: object
>>> t3.ndim # 显示数据维度
2
除此之外,还有:
方法 | 说明 |
---|---|
df.head(n) | 显示头部几行,默认5行 |
df.tail(n) | 显示尾部几行,默认5行 |
df.info() | 显示行数、列数、列索引、列非空值个数、列类型、内存占用 |
df.describe() | 计数、均值、标准差、最大值、四分位数、最小值 |
df.sort_values(by="", ascending=False) | 按照by进行排序,ascending默认为True,即升序 |
(5)DataFrame的索引
df[:] # 取行,结果为DataFrame类型
df[""] # 取列,结果为Series类型
df[:100] # 取前100行
df["count_AnimalName"] # 取count_AnimalName列
df[20:100]["count_AnimalName"] # 取20-100行的count_AnimalName列
当然还有其他的方法:loc和iloc:
loc:通过标签获取数据
>>> t
W X Y Z
a 0 1 2 3
b 4 5 6 7
c 8 9 10 11
>>> t.loc["a", "W"]
0
>>> t.loc["a", ["W", "Z"]]
W 0
Z 3
Name: a, dtype: int64
>>> t.loc["a":"c", ["W", "Z"]]
W Z
a 0 3
b 4 7
c 8 11
iloc:通过位置获取数据
>>> t.iloc[:1, [0, 2]]
W Y
a 0 2
>>> t.iloc[:, 2]
a 2
b 6
c 10
Name: Y, dtype: int64
(6)pandas的布尔索引
>>> t[t["X"]>4]
W X Y Z
b 4 5 6 7
c 8 9 10 11
>>> t[(t["W"]>0) & (t["Z"]<8)]
W X Y Z
b 4 5 6 7
(7)pandas的字符串方法
pandas有许多字符串操作方法,例如我想取出Row_Labels列中字符串长度大于4的行,可以:
df[df["Row_Labels"].str.len() > 4]
通过df.str.方法()
的方式进行调用。具体方法列表此处不展开。
(8)pandas缺失数据的处理
先来看这样一组数据:
>>> t
U V W X Y Z
A NaN 1.0 2.0 3.0 4.0 NaN
B 6.0 7.0 8.0 9.0 0.0 11.0
C 12.0 13.0 14.0 15.0 16.0 17.0
D 18.0 19.0 NaN 21.0 22.0 23.0
数据缺失一般有两种情况,一种是NaN(即np.nan),一种是0。
判断数据是否为nan:
>>> pd.isnull(t)
U V W X Y Z
A True False False False False True
B False False False False False False
C False False False False False False
D False False True False False False
>>> pd.notnull(t)
U V W X Y Z
A False True True True True False
B True True True True True True
C True True True True True True
D True True False True True True
nan处理方法:
方法一:选出不含nan的行列
>>> t1 = t[pd.notnull(t["U"])]
>>> t1
U V W X Y Z
B 6.0 7.0 8.0 9.0 0.0 11.0
C 12.0 13.0 14.0 15.0 16.0 17.0
D 18.0 19.0 NaN 21.0 22.0 23.0
方法二:删除含有nan的行列
df.dropna(axis=0, how="any", inplace=False)
axis指定操作的轴;how默认为any,即一整行只要包含nan就删除,可以改成all,表示只有一整行全都是nan才删除;inplace表示是否将结果直接赋给原变量,默认为False。
>>> t.dropna(how="any")
U V W X Y Z
B 6.0 7.0 8.0 9.0 0.0 11.0
C 12.0 13.0 14.0 15.0 16.0 17.0
方法三:用值填充nan
df.fillna(value)
一般可以使用均值、中位数和0填充nan。
>>> t.fillna(t.mean())
U V W X Y Z
A 12.0 1.0 2.0 3.0 4.0 17.0
B 6.0 7.0 8.0 9.0 0.0 11.0
C 12.0 13.0 14.0 15.0 16.0 17.0
D 18.0 19.0 8.0 21.0 22.0 23.0
我们会发现,对含有nan的列计算平均值时,会自动忽略nan,计算余下值的平均值。这与numpy中不同,在numpy中,nan与任何值运算都是nan。
0值处理方法:
对于不需要处理的0(比如实际值就是0)我们可以不处理;对于需要处理的0,可以统一赋值为nan:
t[t==0] = np.nan
需要注意的是,0会参与均值和中位数的运算。
(9)数据合并
join:把行索引相同的数据合并到一起
>>> t1 = pd.DataFrame(np.zeros((2, 5)), index=list("AB"), columns=list("VWXYZ"))
>>> t1
V W X Y Z
A 0.0 0.0 0.0 0.0 0.0
B 0.0 0.0 0.0 0.0 0.0
>>> t2 = pd.DataFrame(np.ones((3, 4)), index=list("ABC"))
>>> t2
0 1 2 3
A 1.0 1.0 1.0 1.0
B 1.0 1.0 1.0 1.0
C 1.0 1.0 1.0 1.0
>>> t2.join(t1)
0 1 2 3 V W X Y Z
A 1.0 1.0 1.0 1.0 0.0 0.0 0.0 0.0 0.0
B 1.0 1.0 1.0 1.0 0.0 0.0 0.0 0.0 0.0
C 1.0 1.0 1.0 1.0 NaN NaN NaN NaN NaN
>>> t1.join(t2)
V W X Y Z 0 1 2 3
A 0.0 0.0 0.0 0.0 0.0 1.0 1.0 1.0 1.0
B 0.0 0.0 0.0 0.0 0.0 1.0 1.0 1.0 1.0
可以发现,当t2.join(t1)
时,t1的AB行内容直接增加在后面,新增了VWXYZ列,而空缺的C行全为NaN。当t1.join(t2)
时,t2的C行直接被删去了。故谁调用join方法,就以谁为基础。
merge:按照指定的列把数据按照一定方式合并到一起
首先我们定义两个新的数据:
>>> df1 = pd.DataFrame(np.ones((2, 4)), index=list("AB"), columns=list("abcd"))
>>> df2 = pd.DataFrame(np.arange((3, 3)), columns=list("fax"))
>>> df1
a b c d
A 1.0 1.0 1.0 1.0
B 1.0 1.0 1.0 1.0
>>> df2
f a x
0 0 1 2
1 3 4 5
2 6 7 8
然后执行下面的命令:
>>> df1.merge(df2, on="a", how="inner")
a b c d f x
0 1.0 1.0 1.0 1.0 0 2
1 1.0 1.0 1.0 1.0 0 2
我们把df2合并到df1中,并指定参数on为a,意思是按照a列进行合并,参数how的值为inner,表示取交集,inner亦为该参数的默认值。
df1中a列全为1,df2中只有0行为1,1行和2行与df1并无交集,所以df2中0行的f和x列均被合并至df1中的0行和1行。
如果将df2的[1, "a"]处修改为1,再执行该代码,结果如下:
>>> df2.loc[1, "a"] = 1
>>> df2
f a x
0 0 1 2
1 3 1 5
2 6 7 8
>>> df1.merge(df2, on="a")
a b c d f x
0 1.0 1.0 1.0 1.0 0 2
1 1.0 1.0 1.0 1.0 3 5
2 1.0 1.0 1.0 1.0 0 2
3 1.0 1.0 1.0 1.0 3 5
可以看到,df2的a列中有两个1,分别是0行和1行,合并后的结果共有四行,分别是把df2的0行和1行合并至df1的0行,再把df2的0行和1行合并至df1的1行。
下面是how为outer的情况,outer表示取并集:
>>> df1.loc["A", "a"] = 100
>>> df1
a b c d
A 100.0 1.0 1.0 1.0
B 1.0 1.0 1.0 1.0
>>> df2
f a x
0 0 1 2
1 3 1 5
2 6 7 8
>>> df1.merge(df2, on="a", how="outer")
a b c d f x
0 100.0 1.0 1.0 1.0 NaN NaN
1 1.0 1.0 1.0 1.0 0.0 2.0
2 1.0 1.0 1.0 1.0 3.0 5.0
3 7.0 NaN NaN NaN 6.0 8.0
我们会发现,在合并后的第0行,df1的a是100,而df2中没有a为100的行,故以df1为准,空缺位置补NaN。1行和2行与上面的例子相同,不再解释。由于df1仅有2行,又此处是outer,故3行以df2为主,df1空缺处补NaN。
还有左连接和右连接,分别对应left和right,结果如下:
>>> df1.merge(df2, on="a", how="left")
a b c d f x
0 100.0 1.0 1.0 1.0 NaN NaN
1 1.0 1.0 1.0 1.0 0.0 2.0
2 1.0 1.0 1.0 1.0 3.0 5.0
>>> df1.merge(df2, on="a", how="right")
a b c d f x
0 1.0 1.0 1.0 1.0 0 2
1 1.0 1.0 1.0 1.0 3 5
2 7.0 NaN NaN NaN 6 8
这两种模式可以理解为outer的细分,left就是在outer的基础上完全以df1为准,故在outer的结果上删除了3行。right就是在outer的基础上完全以df2为准,所以[0, "a"]位置上的值是1而不是100。
除了直接指定on,还可以分别指定left_on和right_on,例如:
>>> df1.merge(df2, left_on="a", right_on="f")
Empty DataFrame
Columns: [a_x, b, c, d, f, a_y, x]
Index: []
可以看到返回了一个空结果,因为df1的a列和df2的f列并无交集。
(10)数据分组聚合
有时候我们有一组庞大的数据,需要对数据进行分组研究(比如按国家,按性别等等),可以使用分组聚合。
df = pd.read_csv(file_path)
grouped = df.groupby(by="Country") # 按country字段进行分组
grouped
是一个DataFrameGroupBy类型的对象,可以进行遍历或调用聚合方法。
遍历:
for i in grouped:
print(i)
for i, j in grouped:
print(i)
print(j)
每一个i是一个元组,第一个元素是Country的值,第二个元素是一个DataFrame,保存了该Country下的所有其他列的信息。也可以用下面的方法分别遍历第一个元素和第二个元素。
分组之后,可以调用许多聚合方法:
print(grouped.count()) # 统计每个国家的其他所有列总数
print(grouped["Brands"].count()) # 统计每个国家的Brands总数
除此之外,还有平均值、中位数等聚合方法。
方法 | 说明 |
---|---|
count | 非NaN值的数量 |
sum | 非NaN值的和 |
mean | 非NaN值的平均数 |
median | 非NaN值的算数中位数 |
std、var | 无偏(分母为n-1)标准差和方差 |
min、max | 非NaN值的最小值和最大值 |
还可以按照多个条件进行分组:
# 分别按照国家和省份进行分组,返回Series
grouped = df.groupby(by=["Country", "State/Province"])
# 分别按照国家和省份进行分组,并只取Brand列数据,返回Series
grouped = df["Brand"].groupby(by=[df["Country"], df["State/Province"]])
grouped = df.groupby(by=["Country", "State/Province"])["Brand"]
# 分别按照国家和省份进行分组,并只取Brand列数据,返回DataFrame
grouped = df[["Brand"]].groupby(by=[df["Country"], df["State/Province"]])
grouped = df.groupby(by=["Country", "State/Province"])[["Brand"]]
上面的例子中,如果返回的是Series,输出之后会发现仍然有三行,但却是Series类型。前两列都是索引,被称为复合索引。
(11)复合索引
简单的索引操作:
- 获取索引:
df.index
- 指定索引:
df.index = ["x", "y"]
- 重新设置索引:
df.reindex(list("abcd"))
,类似于从df中取出索引为a、b、c、d的四行,如果不存在则全为NaN - 指定某一列作为索引:
df.set_index("Country", drop=False)
,drop为False表示在df中仍然保留Country列的内容 - 返回索引的唯一值:
df.index.unique()
,索引可以重复,unique()
方法同样适用于索引
但假如我使用df.set_index(["a", "b"])
会怎样呢?
>>> df = pd.DataFrame(np.arange(12).reshape((3, 4)), columns=list("abcd"))
>>> df
a b c d
0 0 1 2 3
1 4 5 6 7
2 8 9 10 11
>>> df1 = df.set_index(["a", "b"])
>>> df1
c d
a b
0 1 2 3
4 5 6 7
8 9 10 11
>>> df1.index
MultiIndex([(0, 1),
(4, 5),
(8, 9)],
names=['a', 'b'])
我们会发现df1中有两个索引,分别是a和b,这就是复合索引。
对于Series的复合索引,可以使用t["a", "b"]
的方式来取值,对于DataFrame的复合索引,可以使用df.loc["a"].loc["b"]
的方式来取值。如果我们希望先取第二层索引,再取第一层索引,可以通过df.swaplevel()
交换索引次序。
(12)pandas的时间序列
pd.date_range(start=None, end=None, periods=None, freq="D")
其中:
- start:开始时间
- end:结束时间
- periods:重复次数
- freq:频率
我们来看几个例子:
>>> pd.date_range(start="20200601", end="20200701", freq="D")
DatetimeIndex(['2020-06-01', '2020-06-02', '2020-06-03', '2020-06-04',
'2020-06-05', '2020-06-06', '2020-06-07', '2020-06-08',
'2020-06-09', '2020-06-10', '2020-06-11', '2020-06-12',
'2020-06-13', '2020-06-14', '2020-06-15', '2020-06-16',
'2020-06-17', '2020-06-18', '2020-06-19', '2020-06-20',
'2020-06-21', '2020-06-22', '2020-06-23', '2020-06-24',
'2020-06-25', '2020-06-26', '2020-06-27', '2020-06-28',
'2020-06-29', '2020-06-30', '2020-07-01'],
dtype='datetime64[ns]', freq='D')
>>> pd.date_range(start="20200601", end="20200701", freq="10D")
DatetimeIndex(['2020-06-01', '2020-06-11', '2020-06-21', '2020-07-01'], dtype='datetime64[ns]', freq='10D')
>>> pd.date_range(start="20200601", periods=10, freq="M")
DatetimeIndex(['2020-06-30', '2020-07-31', '2020-08-31', '2020-09-30',
'2020-10-31', '2020-11-30', '2020-12-31', '2021-01-31',
'2021-02-28', '2021-03-31'],
dtype='datetime64[ns]', freq='M')
关于频率的更多缩写:
别名 | 说明 |
---|---|
D | 每日历日 |
B | 每工作日 |
H | 每小时 |
T或min | 每分 |
S | 每秒 |
L或ms | 每毫秒 |
U | 每微秒 |
M | 每月最后一个日历日 |
BM | 每月最后一个工作日 |
MS | 每月第一个日历日 |
BMS | 每月第一个工作日 |
通过下面的命令,可以把时间字符串转换为时间序列:
df["timeStamp"] = pd.to_datetime(df["timeStamp"], format="")
其中df["timpStamp"]
中原本为时间字符串,使用pd.to_datetime()
可以转化为时间索引。format
一般不用写,除非时间格式特殊(比如包含中文)。
(13)重采样
可以使用resample
方法将时间序列从一个频率转化为另一个频率。注意该方法仅针对时间为索引的情况,如果时间不为索引,可以用df.set_index("timeStamp")
进行设置。
将高频率数据转化为低频率数据称为降采样,将地频率数据转化为高频率数据称为升采样。
例如:
t.resample("M").mean() # 按月重采样后计算每月平均值
t.reesample("10D").count() # 按10天从采样后计算每10天的总数
(14)对时间段的处理
在前面讲到的DatetimeIndex
可以理解为对时间戳的处理,这一部分要说一下PeriodIndex
,是对一个时间段的处理。如果数据中保存的时间不是一个完整的数据,而是分别存为了年、月、日,可以用下面的方法转换为时间类型进行处理:
period = pd.PeriodIndex(year, month, day, hour, freq)
前四个参数传入对应的列,再根据freq参数生成一个时间段。
对于时间段的重采样,可以按下面的方法进行:
data = df.set_index(period).resample("10D")