两万字总结python之numpy库

Numpy库介绍

NumPy是一个功能强大的Python库,主要用于对多维数组执行计算。NumPy这个词来源于两个单词-- Numerical和Python。NumPy提供了大量的库函数和操作,可以帮助程序员轻松地进行数值计算。在数据分析和机器学习领域被广泛使用。它有以下几个特点:

  1. numpy内置了并行运算功能,当系统有多个核心时,做某种计算时,numpy会自动做并行计算。
  2. Numpy底层使用C语言编写,内部解除了GIL(全局解释器锁),其对数组的操作速度不受Python解释器的限制,效率远高于纯Python代码。
  3. 有一个强大的N维数组对象Array(一种类似于列表的东西)。
  4. 实用的线性代数、傅里叶变换和随机数生成函数。

Numpy数组和Python列表性能对比:

比如我们想要对一个Numpy数组和Python列表中的每个素进行求平方。那么代码如下:
python列表:

import numpy as np
import time
t1=time.time()
a=[]
for x in range(1000000):
    a.append(x**2)
t2=time.time()
print('python列表耗时:',t2-t1)

结果:

python列表耗时: 0.34757137298583984

numpy数组:

t3=time.time()
b=np.arange(1000000)**2
t4=time.time()
print('numpy数组耗时:',t4-t3)

结果:

numpy数组耗时: 0.003968954086303711

教程地址:

官网:https://docs.scipy.org/doc/numpy/user/quickstart.html。
中文文档:https://www.numpy.org.cn/user_guide/quickstart_tutorial/index.html。

目录:

  • Numpy数组基本用法
  • Numpy数组操作
  • Numpy索引和切片
  • 深拷贝与浅拷贝
  • 文件操作
  • NAN和INF值处理
  • np.random模块
  • Axis理解
  • 通用函数

NumPy数组基本用法

  • Numpy是Python科学计算库,用于快速处理任意维度的数组。
  • Numpy提供一个N维数组类型ndarray,它描述了相同类型的“items”的集合。
  • Numpy.ndarray支持向量化运算。
  • Numpy使用c语言写的,底部解除了GIL,其对数组的操作速度不在受python解释器限制。

numpy中的数组:

Numpy中的数组的使用跟Python中的列表非常类似。他们之间的区别如下:

  1. 一个列表中可以存储多种数据类型。比如a = [1,‘a’]是允许的,而数组只能存储同种数据类型。
  2. 数组可以是多维的,当多维数组中所有的数据都是数值类型的时候,相当于线性代数中的矩阵,是可以进行相互间的运算的。

创建数组(np.ndarray对象):

在Numpy中的数组的数据类型叫做ndarray。

  1. 根据Python中的列表生成:
import numpy as np
a1 = np.array([1,2,3,4])
print(a1)
print(type(a1))
  1. 使用np.arange生成,np.arange的用法类似于Python中的range:
import numpy as np
a2 = np.arange(2,21,2)
print(a2)
  1. 使用np.random生成随机数的数组:
a1 = np.random.random(2,2) # 生成22列的随机数的数组
a2 = np.random.randint(0,10,size=(3,3)) # 元素是从0-10之间随机的33列的数组
  1. 使用函数生成特殊的数组:
import numpy as np
a1 = np.zeros((2,2)) #生成一个所有元素都是022列的数组
a2 = np.ones((3,2)) #生成一个所有元素都是132列的数组
a3 = np.full((2,2),8) #生成一个所有元素都是822列的数组
a4 = np.eye(3) #生成一个在斜方形上元素为1,其他元素都为03x3的矩阵

ndarray常用属性:

ndarray.dtype:

因为数组中只能存储同一种数据类型,因此可以通过dtype获取数组中的元素的数据类型。以下是ndarray.dtype的常用的数据类型:

数据类型 描述 唯一标识符
bool 用一个字节存储的布尔类型(True或False) ‘b’
int8 一个字节大小,-128 至 127 ‘i1’
int16 整数,16 位整数(-32768 ~ 32767) ‘i2’
int32 整数,32 位整数(-2147483648 ~ 2147483647) ‘i4’
int64 整数,64 位整数(-9223372036854775808 ~ 9223372036854775807) ‘i8’
uint8 无符号整数,0 至 255 ‘u1’
uint16 无符号整数,0 至 65535 ‘u2’
uint32 无符号整数,0 至 2 ** 32 - 1 ‘u4’
uint64 无符号整数,0 至 2 ** 64 - 1 ‘u8’
float16 半精度浮点数:16位,正负号1位,指数5位,精度10位 ‘f2’
float32 单精度浮点数:32位,正负号1位,指数8位,精度23位 ‘f4’
float64 双精度浮点数:64位,正负号1位,指数11位,精度52位 ‘f8’
complex64 复数,分别用两个32位浮点数表示实部和虚部 ‘c8’
complex128 复数,分别用两个64位浮点数表示实部和虚部 ‘c16’
object_ python对象 ‘O’
string_ 字符串 ‘S’
unicode_ unicode类型 ‘U’

我们可以看到,Numpy中关于数值的类型比Python内置的多得多,这是因为Numpy为了能高效处理处理海量数据而设计的。举个例子,比如现在想要存储上百亿的数字,并且这些数字都不超过254(一个字节内),我们就可以将dtype设置为int8,这样就比默认使用int64更能节省内存空间了。类型相关的操作如下:

  1. 默认数据类型
import numpy as np
a=np.arange(10)
print(a)
print(a.dtype)
# 如果是windows系统,默认是int32
# 如果是mac或者linux系统,则根据系统来

结果:

[0 1 2 3 4 5 6 7 8 9]
int32
  1. 为每个元素指定数据类型
b=np.array([1,2,3,4,5],dtype=np.int8)#指定每个元素的类型
print(b)
print(b.dtype)

结果:

[1 2 3 4 5]
int8

f=np.array(['a','b'],dtype='S')#字符串的唯一标识符'S'
print(f)
print(f.dtype)

结果:

[b'a' b'b']
|S1
  1. 存储对象
class Person:
    def __init__(self,name,age):
        self.name=name
        self.age=age
d=np.array([Person('张三',18),Person('李四',18)])
print(d)
print(d.dtype)

结果:

[<__main__.Person object at 0x000001B757D7CEE0>
 <__main__.Person object at 0x000001B757D7C910>]
object
  1. 修改每个元素的数据类型
import numpy as np
a1 = np.array([1,2,3])
print(a1.dtype) # window系统下默认是int32
# 以下修改dtype
a2 = a1.astype(np.int64) # astype不会修改数组本身,而是会将修改后的结果返回
print(a2.dtype)

结果:

int32
int64

ndarray.size:获取数组的元素个数

import numpy as np
   a1 = np.array([[1,2,3],[4,5,6]])
   print(a1.size) #打印的是6,因为总共有6个元素

ndarray.ndim:数组的维数

 a1 = np.array([1,2,3])
   print(a1.ndim) # 维度为1
   a2 = np.array([[1,2,3],[4,5,6]])
   print(a2.ndim) # 维度为2
   a3 = np.array([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]])
   print(a3.ndim) # 维度为3

ndarray.shape:

numpy 创建的数组都有一个shape属性,它是一个元组,返回各个维度的维数。有时候我们可能需要知道某一维的特定维数。

代码示例1:

 a1 = np.array([1,2,3])
   print(a1.shape) # 输出(3,),意思是一维数组,有3个数据

   a2 = np.array([[1,2,3],[4,5,6]])
   print(a2.shape) # 输出(2,3),意思是二位数组,23列

   a3 = np.array([
       [
           [1,2,3],
           [4,5,6]
       ],
       [
           [7,8,9],
           [10,11,12]
       ]
   ])
   print(a3.shape) # 输出(2,2,3),意思是三维数组,总共有2个块,每个元素是23列的

代码举例2:

二维情况
>>> import numpy as np
>>> y = np.array([[1,2,3],[4,5,6]])
>>> print(y)
[[1 2 3]
 [4 5 6]]
>>> print(y.shape)
(2, 3)
>>> print(y.shape[0])
2
>>> print(y.shape[1])
3
可以看到y是一个两行三列的二维数组,y.shape[0]代表行数,y.shape[1]代表列数。

三维情况
>>> x  = np.array([[[1,2,3],[4,5,6]],[[7,8,9],[0,1,2]],[[3,4,5],[6,7,8]]])
>>>> print(x)
[[[1 2 3]
  [4 5 6]]

 [[7 8 9]
  [0 1 2]]

 [[3 4 5]
  [6 7 8]]]
>>> print(x.shape)
(3, 2, 3)
>>> print(x.shape[0])
3
>>> print(x.shape[1])
2
>>> print(x.shape[2])
3
可以看到x是一个包含了3个两行三列的二维数组的三维数组,x.shape[0]代表包含二维数组的个数,x.shape[1]表示二维数组的行数,x.shape[2]表示二维数组的列数。

总结
可以看到,shape[0]表示最外围的数组的维数,shape[1]表示次外围的数组的维数,数字不断增大,维数由外到内。

另外,我们还可以通过ndarray.reshape来重新修改数组的维数。示例代码如下:

 a1 = np.arange(12) #生成一个有12个数据的一维数组
   print(a1) 

   a2 = a1.reshape((3,4)) #变成一个2维数组,是34列的
   print(a2)

   a3 = a1.reshape((2,3,2)) #变成一个3维数组,总共有2块,每一块是22列的
   print(a3)

   a4 = a2.reshape((12,)) # 将a2的二维数组重新变成一个12列的1维数组,(12,)表示元组只有一个值时后面需要留空
   print(a4)

   a5 = a2.flatten() # 不管a2是几维数组,都将他变成一个一维数组
   print(a5)

简单总结:

  • reshape(1,12)#改变为二维数组,reshape()可以理解为参数有几个就是就为数组
  • shape结果里面有几个元素就代表是几维数组

ndarray.itemsize:

数组中每个元素占的大小,单位是字节。比如以下代码:

   a1 = np.array([1,2,3],dtype=np.int32)
   print(a1.itemsize) # 打印4,因为每个字节是8位,32/8=4个字节

Numpy数组操作

数组与数的计算:

在Python列表中,想要对列表中所有的元素都加一个数,要么采用map函数,要么循环整个列表进行操作。但是NumPy中的数组可以直接在数组上进行操作。示例代码如下:

import numpy as np
a1 = np.random.random((3,4))
print(a1)
# 如果想要在a1数组上所有元素都乘以10,那么可以通过以下来实现
a2 = a1*10#语法糖
print(a2)
# 也可以使用round让所有的元素只保留2位小数
a3 = a2.round(2)

以上例子是相乘,其实相加、相减、相除也都是类似的。

数组与数组的计算:

对应元素间的相关操作:

import numpy as np
a1 = np.arange(0,24).reshape((3,8))
a2 = np.random.randint(1,10,size=(3,8))
a3 = a1 * a2 #相加/相减/相除/相乘都是可以的 对应位置的元素进行相关
print(a1)
print(a2)
print(a3)

结果:

[[ 0  1  2  3  4  5  6  7]
 [ 8  9 10 11 12 13 14 15]
 [16 17 18 19 20 21 22 23]]
[[5 8 7 1 3 3 8 5]
 [1 2 4 3 7 5 3 8]
 [1 7 2 4 1 3 3 4]]
[[  0   8  14   3  12  15  48  35]
 [  8  18  40  33  84  65  42 120]
 [ 16 119  36  76  20  63  66  92]]

与行数相同并且只有1列的数组之间的运算:

import numpy as np
a1 = np.random.randint(10,20,size=(3,8)) #38列
a2 = np.random.randint(1,10,size=(3,1)) #31列
a3 = a1 - a2 #行数相同,且a2只有1列,能互相运算
print(a1)
print('='*30)
print(a2)
print('='*30)
print(a3)

结果:

[[11 17 14 14 16 19 11 16]
 [10 14 15 10 18 11 19 10]
 [19 19 18 17 17 18 18 10]]
==============================
[[4]
 [3]
 [1]]
==============================
[[ 7 13 10 10 12 15  7 12]
 [ 7 11 12  7 15  8 16  7]
 [18 18 17 16 16 17 17  9]]

观察结果不难发现,a1的每一列的每个元素都会减去a2对应每个元素的值


与列数相同并且只有1行的数组之间的运算:

import numpy as np
a1 = np.random.randint(10,20,size=(3,8)) #38列
a2 = np.random.randint(1,10,size=(1,8))
a3 = a1 - a2
print(a1)
print('='*30)
print(a2)
print('='*30)
print(a3)

结果:

[[19 10 10 10 19 11 16 11]
 [10 17 10 13 17 18 10 15]
 [14 10 14 11 16 18 15 17]]
==============================
[[2 4 1 9 6 3 3 8]]
==============================
[[17  6  9  1 13  8 13  3]
 [ 8 13  9  4 11 15  7  7]
 [12  6 13  2 10 15 12  9]]

观察结果不难发现,a1的每一行的每个元素都会减去a2对应每个元素的值

广播原则:

如果两个数组的后缘维度(trailing dimension,即从末尾开始算起的维度)的轴长度相符或其中一方的长度为1,则认为他们是广播兼容的。广播会在缺失和(或)长度为1的维度上进行。看以下案例分析:

  1. shape为(3,8,2)的数组能和(8,3)的数组进行运算吗?
    分析:不能,因为按照广播原则,从后面往前面数,(3,8,2)和(8,3)中的2和3不相等,所以不能进行运算。

  2. shape为(3,8,2)的数组能和(8,1)的数组进行运算吗?
    分析:能,因为按照广播原则,从后面往前面数,(3,8,2)和(8,1)中的2和1虽然不相等,但是因为有一方的长度为1,所以能参与运算。

  3. shape为(3,1,8)的数组能和(8,1)的数组进行运算吗?
    分析:能,因为按照广播原则,从后面往前面数,(3,1,4)和(8,1)中的4和1虽然不相等且1和8不相等,但是因为这两项中有一方的长度为1,所以能参与运算。

代码举例:

a1=np.array([1,2,3,4])
a2=np.random.randint(1,10,size=(8,1))
a3=a1+a2
print(a1)
print('='*30)
print(a2)
print('='*30)
print(a3)
print('='*30)
print(a3.shape)

结果:

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

数组形状的操作:

可以通过一些函数,非常方便的操作数组的形状。

reshape和resize方法:

  1. reshape是将数组转换成指定的形状,然后返回转换后的结果,对于原数组的形状是不会发生改变的。调用方式:
a1 = np.random.randint(0,10,size=(3,4))
a2 = a1.reshape((2,6)) #将修改后的结果返回,不会影响原数组本身
  1. resize是将数组转换成指定的形状,会直接修改数组本身。并不会返回任何值。调用方式:
a1 = np.random.randint(0,10,size=(3,4))
a1.resize((2,6)) #a1本身发生了改变

flatten和ravel方法:

两个方法都是将多维数组转换为一维数组,但是有以下不同:

  • flatten是将数组转换为一维数组后,然后将这个拷贝返回回去,所以后续对这个返回值进行修改不会影响之前的数组。
  • ravel是将数组转换为一维数组后,将这个视图(可以理解为引用)返回回去,所以后续对这个返回值进行修改会影响之前的数组。
x = np.array([[1, 2], [3, 4]])
x.flatten()[1] = 100 #此时的x[0]的位置元素还是1
x.ravel()[1] = 100 #此时x[0]的位置元素是100

不同数组的组合:

如果有多个数组想要组合在一起,也可以通过其中的一些函数来实现。

  1. vstack:将数组按垂直方向进行叠加。数组的列数必须相同才能叠加。示例代码如下:
a1 = np.random.randint(0,10,size=(3,5))
a2 = np.random.randint(0,10,size=(1,5))
a3 = np.vstack([a1,a2])
  1. hstack:将数组按水平方向进行叠加。数组的行必须相同才能叠加。示例代码如下:
a1 = np.random.randint(0,10,size=(3,2))
a2 = np.random.randint(0,10,size=(3,1))
a3 = np.hstack([a1,a2])

3.concatenate([],axis):将两个数组进行叠加,但是具体是按水平方向还是按垂直方向。则要看axis的参数,如果axis=0,那么代表的是往垂直方向(行)叠加,如果axis=1,那么代表的是往水平方向(列)上叠加,如果axis=None,那么会将两个数组组合成一个一维数组。需要注意的是,如果往水平方向上叠加,那么行必须相同,如果是往垂直方向叠加,那么列必须相同。示例代码如下:

a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6]])
np.concatenate((a, b), axis=0)
# 结果:
array([[1, 2],
    [3, 4],
    [5, 6]])

np.concatenate((a, b.T), axis=1)
# 结果:
array([[1, 2, 5],
    [3, 4, 6]])

np.concatenate((a, b), axis=None)
# 结果:
array([1, 2, 3, 4, 5, 6])

数组的切割:

通过hsplit和vsplit以及array_split可以将一个数组进行切割。

  1. hsplit:按照垂直方向进行切割。用于指定分割成几列,可以使用数字来代表分成几部分,也可以使用数组来代表分割的地方。示例代码如下:
a1 = np.arange(16.0).reshape(4, 4)
np.hsplit(a1,2) #分割成两部分
>>> array([[ 0.,  1.],
     [ 4.,  5.],
     [ 8.,  9.],
     [12., 13.]]), array([[ 2.,  3.],
     [ 6.,  7.],
     [10., 11.],
     [14., 15.]])]

np.hsplit(a1,[1,2]) #代表在下标为1的地方切一刀,下标为2的地方切一刀,分成三部分
>>> [array([[ 0.],
     [ 4.],
     [ 8.],
     [12.]]), array([[ 1.],
     [ 5.],
     [ 9.],
     [13.]]), array([[ 2.,  3.],
     [ 6.,  7.],
     [10., 11.],
     [14., 15.]])]
  1. vsplit:按照水平方向进行切割。用于指定分割成几行,可以使用数字来代表分成几部分,也可以使用数组来代表分割的地方。示例代码如下:
np.vsplit(x,2) #代表按照行总共分成2个数组
>>> [array([[0., 1., 2., 3.],
     [4., 5., 6., 7.]]), array([[ 8.,  9., 10., 11.],
     [12., 13., 14., 15.]])]

np.vsplit(x,(1,2)) #代表按照行进行划分,在下标为1的地方和下标为2的地方分割
>>> [array([[0., 1., 2., 3.]]),
    array([[4., 5., 6., 7.]]),
    array([[ 8.,  9., 10., 11.],
           [12., 13., 14., 15.]])]
  1. split/array_split(array,indicate_or_seciont,axis):用于指定切割方式,在切割的时候需要指定是按照行还是按照列,axis=1代表按照列,axis=0代表按照行。示例代码如下:
np.array_split(x,2,axis=0) #按照垂直方向切割成2部分
>>> [array([[0., 1., 2., 3.],
     [4., 5., 6., 7.]]), array([[ 8.,  9., 10., 11.],
     [12., 13., 14., 15.]])]

数组(矩阵)转置和轴对换:

numpy中的数组其实就是线性代数中的矩阵。矩阵是可以进行转置的。ndarray有一个T属性,可以返回这个数组的转置的结果。示例代码如下:

a1 = np.arange(0,24).reshape((4,6))
a2 = a1.T
print(a2)

另外还有一个方法叫做transpose,这个方法返回的是一个View(可暂时理解为引用),也即修改返回值,会影响到原来数组。示例代码如下:

a1 = np.arange(0,24).reshape((4,6))
a2 = a1.transpose()

为什么要进行矩阵转置呢,有时候在做一些计算的时候需要用到。比如做矩阵的内积的时候。就必须将矩阵进行转置后再乘以之前的矩阵:

a1 = np.arange(0,24).reshape((4,6))
a2 = a1.T
print(a1)
print('='*30)
print(a2)
print('='*30)
print(a1.dot(a2))#dot函数返回两个矩阵的内积

结果:

[[ 0  1  2  3  4  5]
 [ 6  7  8  9 10 11]
 [12 13 14 15 16 17]
 [18 19 20 21 22 23]]
==============================
[[ 0  6 12 18]
 [ 1  7 13 19]
 [ 2  8 14 20]
 [ 3  9 15 21]
 [ 4 10 16 22]
 [ 5 11 17 23]]
==============================
[[  55  145  235  325]
 [ 145  451  757 1063]
 [ 235  757 1279 1801]
 [ 325 1063 1801 2539]]

Numpy索引和切片:

  1. 对一维数组的操作:
import numpy as np
#一维数组进行操作(与python列表切片用法相同)
a1=np.arange(10)#生成个1-9的一维数组
print(a1)
#进行索引操作
print(a1[4])
#进行切片操作
print(a1[4:6])

结果:

[0 1 2 3 4 5 6 7 8 9]
4
[4 5]
  1. 多维数组

索引和切片,索引是不连续的,切片是连续的,若使用逗号分隔,逗号前面是行,逗号后面是列,如果多维数组中只有一个值就是行(需要加中括号),切片无需再加中括号。

import numpy as np
a2=np.random.randint(0,10,size=(4,6))
print(a2)

结果:

[[4 9 9 0 0 9]
 [2 4 4 5 2 0]
 [9 5 4 0 8 5]
 [8 2 3 3 7 4]]
#获取上述矩阵的第一行
print(a2[0])
print('='*30)
#获取中间两行
print(a2[1:3])
print('='*30)
#获取第134print(a2[[0,2,3]])
print('='*30)
#获取第三行第二个数
print(a2[2,1])
print('='*30)
#获取不连续的两个数数
print(a2[[1,2],[2,3]])
print('='*30)
#获取23行,45列的数
print(a2[1:3,3:5])
print('='*30)
#获取列的相关操作:
#获取第一列
print(a2[:,0])
#获取第34print('='*30)
print(a2[:,2:3])

结果:

[ 0  6 12 18]
==============================
[[ 1  7 13 19]
 [ 2  8 14 20]]
==============================
[[ 0  6 12 18]
 [ 2  8 14 20]
 [ 3  9 15 21]]
==============================
8
==============================
[13 20]
==============================
[[19]
 [20]]
==============================
[0 1 2 3 4 5]
==============================
[[12]
 [13]
 [14]
 [15]
 [16]
 [17]]

总结:

  • 数组名[行,列],对于行,列各自又可以使用切片和索引操作
  • 常见操作如下:
    ([索引,索引],[[索引1,索引2],[索引3,索引4]],[:(行切片),:(列切片)],[[x(索引),y,z],[[x1(索引),y1],[x2,y2]…]])
    注意:a[[1,2],[2,3]]表示取第二行第三列和第三行第四列的两个数是不连续的,a[1:2,2:3]表示取第二行第三列和第三行第四列的所有数是连续的.

布尔索引:

布尔运算也是矢量的,比如以下代码:

a1 = np.arange(0,24).reshape((4,6))
print(a1<10) #会返回一个新的数组,这个数组中的值全部都是bool类型

结果:

[[ True  True  True  True  True  True]
 [ True  True  True  True False False]
 [False False False False False False]
 [False False False False False False]]
a1 = np.arange(0,24).reshape((4,6))
a2 = a1 < 10
print(a1[a2]) #这样就会在a1中把a2中为True的元素对应的位置的值提取出来

结果:

[0 1 2 3 4 5 6 7 8 9]

总结:其中布尔运算可以有!=、==、>、<、>=、<=以及&(与)和|(或)。

a1 = np.arange(0,24).reshape((4,6))
a2 = a1[(a1 < 5) | (a1 > 10)]
print(a2)

值的替换:

利用索引,也可以做一些值的替换。把满足条件的位置的值替换成其他的值。比如以下代码:

a1 = np.arange(0,24).reshape((4,6))
a1[3] = 0 #将第三行的所有值都替换成0
print(a1)

也可以使用条件索引来实现:

a1 = np.arange(0,24).reshape((4,6))
a1[a1 < 5] = 0 #将小于5的所有值全部都替换成0
print(a1)

还可以使用函数来实现:

# where函数:
a1 = np.arange(0,24).reshape((4,6))
a2 = np.where(a1 < 10,1,0) #把a1中所有小于10的数全部变成1,其余的变成0
print(a2)

深拷贝与浅拷贝

在操作数组的时候,它们的数据有时候拷贝进一个新的数组,有时候又不是。这经常是初学者感到困惑。下面有三种情况:

不拷贝:

a = np.arange(12)
b = a #这种情况不会进行拷贝
print(b is a) #返回True,说明b和a是相同的

View或者浅拷贝:

有些情况,会进行变量的拷贝,但是他们所指向的内存空间都是一样的,那么这种情况叫做浅拷贝,或者叫做View(视图)。比如以下代码:

a = np.arange(12)
c = a.view()
print(c is a) #返回False,说明c和a是两个不同的变量
c[0] = 100
print(a[0]) #打印100,说明对c上的改变,会影响a上面的值,说明他们指向的内存空间还是一样的,这种叫做浅拷贝,或者说是view

深拷贝:

将之前数据完完整整的拷贝一份放到另外一块内存空间中,这样就是两个完全不同的值了。示例代码如下:

a = np.arange(12)
d = a.copy()
print(d is a) #返回False,说明d和a是两个不同的变量
d[0] = 100
print(a[0]) #打印0,说明d和a指向的内存空间完全不同了。

例子:

像之前讲到的flatten和ravel就是这种情况,ravel返回的就是View,而flatten返回的就是深拷贝。

文件操作

操作CSV文件:

文件保存:

有时候我们有了一个数组,需要保存到文件中,那么可以使用np.savetxt来实现。相关的函数描述如下:

np.savetxt(frame, array, fmt='%.18e', delimiter=None)
* frame : 文件、字符串或产生器,可以是.gz或.bz2的压缩文件
* array : 存入文件的数组
* fmt : 写入文件的格式,例如:%d %.2f %.18e
* delimiter : 分割字符串,默认是任何空格

以下是使用的例子:

a = np.arange(100).reshape(5,20)
np.savetxt("a.csv",a,fmt="%d",delimiter=",")

读取文件:

有时候我们的数据是需要从文件中读取出来的,那么可以使用np.loadtxt来实现。相关的函数描述如下:

np.loadtxt(frame, dtype=np.float, delimiter=None, unpack=False)
* frame:文件、字符串或产生器,可以是.gz或.bz2的压缩文件。
* dtype:数据类型,可选。
* delimiter:分割字符串,默认是任何空格。
* skiprows:跳过前面x行。
* usecols:读取指定的列,用元组组合。
* unpack:如果True,读取出来的数组是转置后的。

np独有的存储解决方案:

numpy中还有一种独有的存储解决方案。文件名是以.npy或者npz结尾的。以下是存储和加载的函数。

  • 存储:np.save(fname,array)或np.savez(fname,array)。其中,前者函数的扩展名是.npy,后者的扩展名是.npz,后者是经过压缩的。
  • 加载:np.load(fname)。

CSV文件操作:

读取csv文件:

import csv

with open('stock.csv','r') as fp:
    reader = csv.reader(fp)
    titles = next(reader)#跳过第一行,next会向指针下移
    for x in reader:
        print(x)

这样操作,以后获取数据的时候,就要通过下表来获取数据。如果想要在获取数据的时候通过标题来获取。那么可以使用DictReader。示例代码如下:

with open( 'stock.csv' ,'r') as fp:
#使用DictReader创建的reader对象
#不会包含标题那行的数据
#reader是一个迭代器,遍历这个迭代器,返回来的是一个字典。
reader = csv.DictReader(fp)
for x in reader:
	value = {
      "name" :x [ 'secShortName ' ], ' volumn ' :x [ ' turnoverVol']}
	print(value)

写入数据到csv文件:

写入数据到csv文件,需要创建一个writer对象,主要用到两个方法。一个是writerow,这个是写入一行。一个是writerows,这个是写入多行。示例代码如下:

import csv

headers = ['name','age','classroom']
values = [
    ('zhiliao',18,'111'),
    ('wena',20,'222'),
    ('bbc',21,'111')
]
with open('test.csv','w',newline='') as fp:
    writer = csv.writer(fp)
    writer.writerow(headers)
    writer.writerows(values)

也可以使用字典的方式把数据写入进去。这时候就需要使用DictWriter了。示例代码如下:

import csv

headers = ['name','age','classroom']
values = [
    {
     "name":'wenn',"age":20,"classroom":'222'},
    {
     "name":'abc',"age":30,"classroom":'333'}
]
with open('test.csv','w',newline='') as fp:
    writer = csv.DictWriter(fp,headers)
    writer.writerow({
     'name':'zhiliao',"age":18,"classroom":'111'})
    writer.writerows(values)

注意:当需要写入表头:

writer =csv. DictWriter(fp, headers)
#写入表头数据的时候,需要调用writeheader方法
writer.witeheader ()

NAN和INF值处理

首先我们要知道这两个英文单词代表的什么意思:

  • NAN:Not A number,不是一个数字的意思,但它是属于浮点类型的,所以想要进行数据操作的时候需要注意它的类型。
  • INF:Infinity,代表的是无穷大的意思,也是属于浮点类型。np.inf表示正无穷大,-np.inf表示负无穷大,一般在出现除数为0的时候为无穷大。比如2/0。

NAN一些特点:

  • NAN和NAN不相等。比如np.NAN != np.NAN这个条件是成立的。
  • NAN和任何值做运算,结果都是NAN。

有些时候,特别是从文件中读取数据的时候,经常会出现一些缺失值。缺失值的出现会影响数据的处理。因此我们在做数据分析之前,先要对缺失值进行一些处理。处理的方式有多种,需要根据实际情况来做。一般有两种处理方式:删除缺失值,用其他值进行填充。

删除缺失值:

有时候,我们想要将数组中的NAN删掉,那么我们可以换一种思路,就是只提取不为NAN的值。示例代码如下:

# 1. 删除所有NAN的值,因为删除了值后数组将不知道该怎么变化,所以会被变成一维数组
data = np.random.randint(0,10,size=(3,5)).astype(np.float)
data[0,1] = np.nan
data = data[~np.isnan(data)] # 此时的data会没有nan,并且变成一个1维数组

# 2. 删除NAN所在的行
data = np.random.randint(0,10,size=(3,5)).astype(np.float)
# 将第(0,1)(1,2)两个值设置为NAN
data[[0,1],[1,2]] = np.NAN
# 获取哪些行有NAN
lines = np.where(np.isnan(data))[0]
# 使用delete方法删除指定的行,axis=0表示删除行,lines表示删除的行号
data1 = np.delete(data,lines,axis=0)

注意:
除了deiete用axis=0表示行以外,其他的大部分函数都是axis=1来表示行。

用其他值进行替代:

数学 英语
59 89
90 32
78 45
34 NAN
NAN 56
23 56

如果想要求每门成绩的总分,以及每门成绩的平均分,那么就可以采用某些值替代。比如求总分,那么就可以把NAN替换成0,如果想要求平均分,那么就可以把NAN替换成其他值的平均值。示例代码如下:

scores = np.loadtxt("nan_scores.csv",skiprows=1,delimiter=",",encoding="utf-8",dtype=np.str)
scores[scores == ""] = np.NAN
scores = scores.astype(np.float)
# 1. 求出学生成绩的总分
scores1 = scores.copy()
socres1.sum(axis=1)

# 2. 求出每门成绩的平均分
scores2 = scores.copy()
for x in range(scores2.shape[1]):
    score = scores2[:,x]
    non_nan_score = score[score == score]
    score[score != score] = non_nan_score.mean()
print(scores2.mean(axis=0))

np.random模块

np.random为我们提供了许多获取随机数的函数。这里统一来学习一下。

np.random.seed:

用于指定随机数生成时所用算法开始的整数值,如果使用相同的seed()值,则每次生成的随即数都相同,如果不设置这个值,则系统根据时间来自己选择这个值,此时每次生成的随机数因时间差异而不同。一般没有特殊要求不用设置。以下代码:

np.random.seed(1)
print(np.random.rand()) # 打印0.417022004702574
print(np.random.rand()) # 打印其他的值,因为随机数种子只对下一次随机数的产生会有影响。

np.random.rand:

生成一个值为[0,1)之间的数组,形状由参数指定,如果没有参数,那么将返回一个随机值。示例代码如下:

data1 = np.random.rand(2,3,4) # 生成234列的数组,值从0-1之间
data2 = np.random.rand() #生成一个0-1之间的随机数

np.random.randn:

生成均值(μ)为0,标准差(σ)为1的标准正态分布的值。示例代码如下:

data = np.random.randn(2,3) #生成一个23列的数组,数组中的值都满足标准正太分布

np.random.randint:

生成指定范围内的随机数,并且可以通过size参数指定维度。示例代码如下:

data1 = np.random.randint(10,size=(3,5)) #生成值在0-10之间,35列的数组
data2 = np.random.randint(1,20,size=(3,6)) #生成值在1-20之间,36列的数组

np.random.choice:

从一个列表或者数组中,随机进行采样。或者是从指定的区间中进行采样,采样个数可以通过参数指定:

data = [4,65,6,3,5,73,23,5,6]
result1 = np.random.choice(data,size=(2,3)) #从data中随机采样,生成23列的数组
result2 = np.random.choice(data,3) #从data中随机采样3个数据形成一个一维数组
result3 = np.random.choice(10,3) #从0-10之间随机取3个值

np.random.shuffle:

把原来数组的元素的位置打乱。示例代码如下:

a = np.arange(10)
np.random.shuffle(a) #将a的元素的位置都会进行随机更换

更多:

更多的random模块的文档,请参考Numpy的官方文档:https://docs.scipy.org/doc/numpy/reference/routines.random.html

Axis理解

简单来说, 最外面的括号代表着 axis=0,依次往里的括号对应的 axis 的计数就依次加 1。什么意思呢?下面再来解释一下。
两万字总结python之numpy库_第1张图片
最外面的括号就是axis=0,里面两个子括号axis=1。 操作方式:如果指定轴进行相关的操作,那么他会使用轴下的每个直接子元素的第0个位置,第1个位置,第2个位置…分别进行相关的操作。
现在我们用刚刚理解的方式来做几个操作。比如现在有一个二维的数组:

x = np.array([[0,1],[2,3]])
  1. 求x数组在axis=0和axis=1两种情况下的和:
>>> x.sum(axis=0)
 array([2, 4])

为什么得到的是[2,4]呢,原因是我们按照axis=0的方式进行相加,那么就会把最外面轴下的所有直接子元素中的第0个位置进行相加,第1个位置进行相加…依此类推,得到的就是0+2以及2+3,然后进行相加,得到的结果就是[2,4]。

 >>> x.sum(axis=1)
 array([1, 5])

因为我们按照axis=1的方式进行相加,那么就会把轴为1里面的元素拿出来进行求和,得到的就是0,1,进行相加为1,以及2,3进行相加为5,所以最终结果就是[1,5]了。

  1. 用np.max求axis=0和axis=1两种情况下的最大值:
import  numpy as np
np.random.seed(100)
x = np.random.randint(0,10,size=(3,5))
print(x)
x.max(axis=0)

结果:

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

因为我们是按照axis=0进行求最大值,那么就会在最外面轴里面找直接子元素,然后将每个子元素的第0个值放在一起求最大值,将第1个值放在一起求最大值,以此类推。而如果axis=1,那么就是拿到每个直接子元素,然后求每个子元素中的最大值:

x.max(axis=1)

结果:

array([8, 5, 8])
  1. 用np.delete在axis=0和axis=1两种情况下删除元素:

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

 >>> np.delete(x,0,axis=0)#
 array([[2, 3]])

np.delete是个例外。我们按照axis=0的方式进行删除,那么他会首先找到最外面的括号下的直接子元素中的第0个,然后删掉,剩下最后一行的数据。

>>> np.delete(x,0,axis=1)
 array([[1],
        [3]])

同理,如果我们按照axis=1进行删除,那么会把第一列的数据删掉。

对于delete函数:
numpy中的delete函数有三个参数:
numpy.delete(arr, obj, axis)
arr:需要处理的矩阵
obj:在什么位置处理
axis:这是一个可选参数,axis = None,1,0

axis=None:arr会先按行展开,然后按照obj,删除第obj-1(从0开始)位置的数,返回一个行矩阵。

axis = 0:arr按行删除

axis = 1:arr按列删除

三维以上数组:

两万字总结python之numpy库_第2张图片
两万字总结python之numpy库_第3张图片

通用函数

一元函数:

函数 描述
np.abs 绝对值
np.sqrt 开根
np.square 平方
np.exp 计算指数(e^x)
np.log,np.log10,np.log2,np.log1p 求以e为底,以10为低,以2为低,以(1+x)为底的对数
np.sign 将数组中的值标签化,大于0的变成1,等于0的变成0,小于0的变成-1
np.ceil 朝着无穷大的方向取整,比如5.1会变成6,-6.3会变成-6
np.floor 朝着负无穷大方向取证,比如5.1会变成5,-6.3会变成-7
np.rint,np.round 返回四舍五入后的值
np.modf 将整数和小数分隔开来形成两个数组
np.isnan 判断是否是nan
np.isinf 判断是否是inf
np.cos,np.cosh,np.sin,np.sinh,np.tan,np.tanh 三角函数
np.arccos,np.arcsin,np.arctan 反三角函数

二元函数:

函数 描述
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 取整运算,相当于//
np.mod 取余运算,相当于%
greater,greater_equal,less,less_equal,equal,not_equal >,>=,<,<=,=,!=的函数表达式
logical_and &的函数表达式
logical_or |的函数表达式

聚合函数:

安全版本即元素值为NAN也不影响相应计算

函数名称 NAN安全版本 描述
np.sum np.nansum 计算元素的和
np.prod np.nanprod 计算元素的积
np.mean np.nanmean 计算元素的平均值
np.std np.nanstd 计算元素的标准差
np.var np.nanvar 计算元素的方差
np.min np.nanmin 计算元素的最小值
np.max np.nanmax 计算元素的最大值
np.argmin np.nanargmin 找出最小值的索引
np.argmax np.nanargmax 找出最大值的索引
np.median np.nanmedian 计算元素的中位数

使用np.sum或者是a.sum即可实现。并且在使用的时候,可以指定具体哪个轴。同样Python中也内置了sum函数,但是Python内置的sum函数执行效率没有np.sum那么高,可以通过以下代码测试了解到:

a = np.random.rand(1000000)
%timeit sum(a) #使用Python内置的sum函数求总和,看下所花费的时间
%timeit np.sum(a) #使用Numpy的sum函数求和,看下所花费的时间

结果:

73.3 ms ± 1.47 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
899 µs ± 39.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

解释:

  • 对单行代码执行计时 :%timeit
  • 对多行代码执行计时:%%timeit:

要在ipython下才可以使用。(所以说Jupyter Notebook当然是可以用的,pycharm里的python环境也是jupyter Notebook的)
%timeit可以测量一行代码多次执行的时间
%%timeit可以测量多行代码多次执行的时间

布尔数组的函数:

函数名称 描述
np.any 验证任何一个元素是否为真
np.all 验证所有元素是否为真

比如想看下数组中是不是所有元素都为0,那么可以通过以下代码来实现:

np.all(a==0) 
# 或者是
(a==0).all()

比如我们想要看数组中是否有等于0的数,那么可以通过以下代码来实现:

np.any(a==0)
# 或者是
(a==0).any()

排序:

  1. np.sort:指定轴进行排序。默认是使用数组的最后一个轴进行排序。
a = np.random.randint(0,10,size=(3,5))
b = np.sort(a) #按照行进行排序,因为最后一个轴是1,那么就是将最里面的元素进行排序。
c = np.sort(a,axis=0) #按照列进行排序,因为指定了axis=0
print(a)
print('='*30)
print(b)
print('='*30)
print(c)

结果:

[[1 6 5 9 2]
 [1 6 8 7 2]
 [8 5 5 3 8]]
==============================
[[1 2 5 6 9]
 [1 2 6 7 8]
 [3 5 5 8 8]]
==============================
[[1 5 5 3 2]
 [1 6 5 7 2]
 [8 6 8 9 8]]

还有ndarray.sort(),这个方法会直接影响到原来的数组,而不是返回一个新的排序后的数组。

  1. np.argsort:返回排序后的下标值。示例代码如下:
np.argsort(a) #默认也是使用最后的一个轴来进行排序。

结果:

array([[0, 4, 2, 1, 3],#这下标是原数组a的值,即原数组a的第一行第一个元素排在了第一位,第五个元素排在了第2...
       [0, 4, 1, 3, 2],
       [3, 1, 2, 0, 4]], dtype=int64)
  1. 降序排序:np.sort默认会采用升序排序。如果我们想采用降序排序。那么可以采用以下方案来实现:
 # 1. 使用负号
 -np.sort(-a)

 # 2. 使用sort和argsort以及take
 indexes = np.argsort(-a) #排序后的结果就是降序的
 np.take(a,indexes) #从a中根据下标提取相应的元素

其他函数补充:

  1. np.apply_along_axis:沿着某个轴执行指定的函数。示例代码如下:
 # 求数组a按行求均值,并且要去掉最大值和最小值。
 np.apply_along_axis(lambda x:x[(x != x.max()) & (x != x.min())].mean(),axis=1,arr=a)#会把数组的每一行传给x
  1. np.linspace:用来将指定区间内的值平均分成多少份。示例代码如下:
 # 将0-1分成12分,生成一个数组
 np.linspace(0,1,12)
  1. np.unique:返回数组中的唯一值。
 # 返回数组a中的唯一值,并且会返回每个唯一值出现的次数。
 np.unique(a,return_counts=True)

更多:

https://docs.scipy.org/doc/numpy/reference/index.html

你可能感兴趣的:(python机器学习,数据挖掘,数据分析必备计算分析库,python,numpy,机器学习,数据分析)