Numpy关于数据

Numpy读取保存数据

​ 数据一般都被记录在存储当中,我们常见的小规模数据大多是 Excel 的 xlsx,csv, json, 或 txt,log 等等格式。 当我们要处理数据,特别是运算数据的时候,我们都能拿 Python 读取出来。而在大数据这种情况下,数据是存储在 MySQL,Mongodb 等这种数据库中的, 同样,Python 也提供了很多接口来读取这些数据。

​ 今天我们想聊聊其中的一部分,用 Numpy 来直接读取数据(通常是纯数值形式的数据),并加以运算的流程。而且看看我们可以如何保存 Numpy 里的数据。

加载常用数据格式

  • np.loadtxt(), np.fromstring()

​ 一般我们都把数据存放在文件中,数据一般长这样:

![](http://cdn.leafii.top/img/Screen Shot 2022-06-02 at 22.36.26.png)

​ 如果用Excel打开数据,也有可能像下面这样:

​ 下面,请使用已经为你准备好了的数据,从上篇文章的里面拷贝了一份,作为csv格式存起来了,使用以下的代码查看文件中的数据:

import os
print("data file in directory:", os.listdir("data"))
with open("data/data.csv", "r") as f:
    print("\n", f.read())

​ 运行结果

data file in directory: ['data.csv', 'data.txt']

 # StudentID, Age, Score
20131,10,67
20132,11,88
20133,12,98
20134,8,100
20135,9,75
20136,12,78

​ 如果现在要模拟经常要加载的情况,从文件中加载出来。如果使用纯python,可以一行一行读出来,然后保存在list中;但是也可以直接用numpy的方式读取出来:使用np.loadtxt()功能,我们就可以自定义地读取出来数据了。

import numpy as np
data = np.loadtxt("data/data.csv", delimiter=",", skiprows=1, dtype=np.int)
print(data)

​ 运行结果

[[20131    10    67]
 [20132    11    88]
 [20133    12    98]
 [20134     8   100]
 [20135     9    75]
 [20136    12    78]]

​ 在loadtxt的参数中,我们传入让numpy在做数据分隔的时候,以逗号作为分隔符。同时skiprow可以skip掉第一行的描述型文字(默认也会自动skip掉第一行),还有读取出来的数据要放到定义好了的`dtype=np.int类型的array中,这一行的功能就可以轻松读取这个csv文件的数据了。

​ 请注意:np.loadtxt()为什么不叫np.loadcsv()呢?因为csv数据也属于纯文本数据,如果想要加载后缀名为txt的文件,也是没问题的。以上就是最普通的一种加载数据方式。下面我们来看一种更加有趣的加载过程。能不能直接从字符中加载数据呢?

​ 有时候,我需要对字符串加工处理后,直接读这个加工之后的字符串。

row_string = "20131, 10, 67, 20132, 11, 88, 20133, 12, 98, 20134, 8, 100, 20135, 9, 75, 20136, 12, 78"
data = np.fromstring(row_string, dtype=np.int, sep=",")
data = data.reshape(6, 3)
print(data)

​ 运行结果

[[20131    10    67]
 [20132    11    88]
 [20133    12    98]
 [20134     8   100]
 [20135     9    75]
 [20136    12    78]]

​ 请注意,这种方法目前只能读取一个数值序列,读完这个序列之后,可以再使用numpy的reshape方法把数据定义成你想要的样子。另外要注意的一点是对比np.loadtxt(delimiter=",")np.fromstring(sep=",")这两个用来判断分隔符的参数,它们的参数命名是不一样的。

​ numpy可以读取csvtxt格式的数据,但是无法读取Excel中的xlsx数据格式,未来可以使用Pandas库进行xlsx数据格式的读取。

保存数据

​ 在日常生活中经常会碰到的数据格式,都能用Numpy读,在读取完之后或者处理完数据之后,如果想把数据保存起来,怎么办呢?好在Numpy还是挺人性化的,也有丰富的方法来处理保存数据这件事。

  • np.savetxt()

​ Numpy存数据,存哪种数据格式,取决于你想不想这份数据被人查看,或者被其他语言编辑,如果想的话,你就会保存成一些通用的数据格式,比如csvtxt

print("numpy data:\n", data)
np.savetxt("data/save_data.csv", data, delimiter=",", fmt='%s')

print("data file in directory:", os.listdir("data"))
with open("data/save_data.csv", "r") as f:
    print("\n", f.read())

​ 运行结果

numpy data:
 [[20131    10    67]
 [20132    11    88]
 [20133    12    98]
 [20134     8   100]
 [20135     9    75]
 [20136    12    78]]
data file in directory: ['data.csv', 'data.txt', 'save_data.csv']

 20131,10,67
20132,11,88
20133,12,98
20134,8,100
20135,9,75
20136,12,78
  • np.save(), np.savez(), np.savez_compressed()

​ 还有一些Numpy独有的模式,那就是用二进制的格式保存。如果你没有想让别人看你的数据,你只想自己使用Numpy时加载的话,那你完全就可以用这种方式存储下来。请注意,使用np.save()来保存,保存的是一个以.npy结尾的二进制文件。加载的时候,我们能用np.load()直接加载这个二进制数据文件。

np.save("data/save_data.npy", data)
print("data file in directory:", os.listdir("data"))
npy_data = np.load("data/save_data.npy")
print(npy_data)

​ 运行结果

data file in directory: ['data.csv', 'data.txt', 'save_data.csv', 'save_data.npy']
[[20131    10    67]
 [20132    11    88]
 [20133    12    98]
 [20134     8   100]
 [20135     9    75]
 [20136    12    78]]

​ 还有一种神奇的保存方式,在一个numpy文件中保存多个numpy array。有时候,你是分开多个array来存放不同类型的数据,比如机器学习中的traindata和testdata。这时我们能用np.savez()保存一个.npz文件将这两个array同时存储好。

train_data = np.array([1,2,3])
test_data = np.array([11,22,33])
np.savez("data/save_data.npz", train=train_data, test=test_data)
print("data file in directory:", os.listdir("data"))

​ 运行结果

data file in directory: ['data.csv', 'data.txt', 'save_data.csv', 'save_data.npy', 'save_data.npz']

np.savez() 第二、三个参数名 train=xx, test=xx 其实是可以自定义的,这些参数名会作为之后我们加载回来的索引标签。

npz_data = np.load("data/save_data.npz")
print("train:", npz_data["train"])
print("test:", npz_data["test"])

​ 运行结果

train: [1 2 3]
test: [11 22 33]

np.savez() 的时候,还有一个方法可以让你更节省空间,那就是用 np.savez_compressed() 来做一次数据压缩。

np.savez_compressed("data/save_data_compressed.npz", train=train_data, test=test_data)
print("data file in directory:", os.listdir("data"))
npz_data_compressed = np.load("data/sace_data_compressed.npz")
print("train:", npz_compressed["train"])
print("test:", npz_data_compressed["test"])
import os
print("compressed file size:", os.path.getsize("data/save_data_compressed.npz"))
print("original file size:", os.path.getsize("data/save_data.npz"))

​ 运行结果

data file in directory: ['data.csv', 'data.txt', 'save_data.csv', 'save_data.npy', 'save_data.npz', 'save_data_compressed.npz']
train: [1 2 3]
test: [11 22 33]
compressed file size: 402
original file size: 528

总结

​ 说了这么多用 numpy 的方式读取与保存的方法,可见 numpy 还是很用心在做数据这件事的。如果你喜欢纯文本,我们看上去比较有意义的存储格式,那你就用 txt 或者 csv 这种方式,如果你追求存储空间和速度,我建议还是用二进制来存储吧~

标准数据生成

​ 数据是多变的,不光是数据的形态多变,里面的数值也是多变的。通常,我们在创建一个Numpy Array的时候,是想带着一些数值来初始化的,比如我想先要一个全是0的Array

​ 所以这节内容我们来说一说怎么带着数值初始化,可以带着怎样的数值初始化。

创建统一数据

  • np.zeros(),np.ones(),np.full()

​ 首先来点简单的,初始化Array的时候,让他们全部为某数。最简单的就是全0或者全1的数据。

import numpy as np
zeros = np.zeros([2,3])
print("zeros:\n", zeros)

ones = np.ones([3,2])
print("\nones:\n", ones)

​ 运行结果

zeros:
 [[0. 0. 0.]
 [0. 0. 0.]]

ones:
 [[1. 1.]
 [1. 1.]
 [1. 1.]]

​ 当我们创建这些数据时,我们不仅仅是可以给定创建的具体数值是0还是1,而且还能指定这个数据的shape是什么。创建出来之后,就可以把它当作普通的Array数据使用就好了,要reshape,transpose,sum等操作,都是可以进行的。

​ 我们可以创建0或1的数据,那我们能不能创建其他数值的数据?答案是肯定的,使用np.full()功能就好了。同样,我们先输入这份数据的shape,然后指定这份数据全部元素的数值。

nines = np.full([2,3], 9)
print(nines)
#[[9 9 9]
# [9 9 9]]

​ 在处理shape的时候,有一件有趣的事:如果我们手头已经有一份数据,我们想创建一个和它类型一样,大小一样的另一份数据,我们可以调用np.xxx_like()这种形式的功能。看下面的例子:

data = np.array([
  [1,2,3],
  [4,5,6]
], dtype=np.int)
ones = np.ones(data.shape, dtype=data.dtype)
ones_like = np.ones_like(data)

print("ones:", ones.shape, ones.dtype)
print("ones_like:", ones_like.shape, ones_like.dtype)
print("ones_like value:\n", ones_like)

​ 运行结果

ones: (2, 3) int32
ones_like: (2, 3) int32
ones_like value:
 [[1 1 1]
 [1 1 1]]
  • np.zeros_like(),np.ones_like(),np.full_like()

​ 因为dtype和shape都和源数据一样,那么它们之间做加减乘除运算就很契合了。其实ones_like()算是一种偷懒功能;同理,我们还有np.zeros_like()np.full_like()这两种。

print(np.zeros_like(data))
print(np.full_like(data,6)

​ 运行结果

[[0 0 0]
 [0 0 0]]
[[6 6 6]
 [6 6 6]]

创建规则数据

  • np.arange(), np.linspace()

​ 而创建有些规律的数据也是Numpy的拿手好戏。首先说一个最常见的arange功能,用来得到一个序列,如:

print("python range:", list(range(5)))
# python range: [0, 1, 2, 3, 4]
print("numpy arange:", np.arange(5))
# numpy arange: [0 1 2 3 4]

​ 同样,np.arange()也可以像range()一样,对范围做自定义变更或者跳跃取值。

# (start, end, step)
print("python range:", list(range(3, 10, 2)))
# python range: [3, 5, 7, 9]
print("numpy arange:", np.arange(3, 10, 2))
# numpy arange: [3 5 7 9]

​ 还有一个也是用来取一段数字中的值,这个也比较常用,特别是在画折线图的时候,我想要连续在一个区间内取间隔一致的数据点。里面的参数分别代表从start的值到end的值,一共返回这中间num个数据点。

# (start, end, num)
print("linspace:", np.linspace(-1, 1, 5))
# linspace: [-1.  -0.5  0.   0.5  1. ]

​ 更厉害的是,有时候我们会很纠结,上面是在-1 至 1 之间分了 4 个区域。 而我们想在-1 至 1 之间分 5 个区域,怎么搞?加一个 endpoint=False 就可以返回这 5 个区域的结节点了。

print("5 segments:", np.linspace(-1, 1, 5, endpoint=False))
# 5 segments: [-1.  -0.6 -0.2  0.2  0.6]

​ 还有很多做特殊规则的数据的方式,比如 np.identity(), np.eye(), np.logspace() 等等, 这个教学我先介绍一些常用的,当你有需要的时候,你再单个搜索就行了。

快速创建再添加值

  • np.empty(), np.empty_like()

​ 和 np.ones() 这种很相似的,有一个叫 np.empty() 功能,我想单独拎出来介绍一下。 如果你对运算速度有要求,你就得仔细听一下。

np.empty() 功能,不会初始化新建 array 里面的数值,所以你会看到这里面的数值都是乱乱的。 注意,虽然乱乱的,但是它不是随机数哦,你不能把它当随机数使用。 想用随机数的话,我后面有单独一个教学讲随机数。

print(np.empty([4,3]))

​ 运行结果

[[9.03433771e-308 5.09847862e-304 3.53009707e-305]
 [7.52473700e-302 8.90332017e-305 8.01478727e-302]
 [2.34157518e-301 6.86145138e-302 3.06135986e-301]
 [1.17801178e-300 8.83208360e-302 1.14834727e-302]]

​ 不能作随机数,又没有具体数值,那么empty为什么存在呢?

  1. 可以把它当成一个placeholder,作为一个容器先放着,之后满满的放数据进去
  2. 创建起来比ones,zeros,full都快一点

​ 所以在这种情况下,我们才想使用np.empty(),编写代码查看用两种方法创建的速度:

import time
t0 = time.time()
for _ in range(1000):
    _ = np.ones([100, 100])
t1 = time.time()
for _ in range(1000):
    _ = np.empty([100, 100])
t2 = time.time()
print("ones time:", t1 - t0)
print("empty time:", t2 - t1)
#ones time: 0.015000104904174805
#empty time: 0.002000093460083008

​ 因此我们能知道empty比ones快了很多很多,给你看一下它的日常使用场景:

import random

empty = np.empty([2,3])
print("empty before:\n", empty)
data = np.arange(6).reshape([2, 3])
for i in range(data.shape[0]):
    for j in range(data.shape[1]):
        empty[i, j] = data[i, j] * random.random()
print("empty after:\n", empty)

​ 所以一般当我有一个数据要根据另一份数据生成的时候,无论我初始化是什么值,到最终都要全部被替换的时候,我就喜欢用 np.empty()。 ok,最后说一句,有 np.empty(),我们也有 np.empty_like() 用法和 np.zeros_like() 这种类似,很方便用的。

总结

​ 现在你能够按自己的想法初始化最符合你需要的数据啦。

随机数和随机操作

​ 计算机能模拟随机,是一件十分重要的事。因为计算机本来是一种执行确定步骤,返回确定结果的机器。但是你想要它模拟随机,就得有一些特殊处理。 好在 Python 自己有专门处理随机的功能。而 Numpy 作为 Python 的网红库,当然也自带了很丰富的随机功能。比如各式随机数的生成方式, 随机化当前数列等。甚至你还能加随机,复现一些随机后的结果。

多种随机数生成

​ 提到随机,首先我们想到的当然是生成一批随机数,对这批随机数做计算啦。在深度学习中,这是一件非常重要的事,比如你常会随机生成神经网络的权重,生成遗传算法中的基因序列等等。

  • np.random.rand(), np.random.random()

​ 假设现在没有 Numpy,我们当然也能用 Python 自带的 random 来解决,但是效率会低不少。 用 Python 自带的 random,大概是怎么用的呢?

import random
print(random.random())
print(random.randint(1, 10))
# 0.5015630814097707
# 10

​ 但是Python的random没有考虑数组类型的高效数据结构,所以我们在array类型的数据结构时,更喜欢直接使用Numpy来生成。

import numpy as np
dim1, dim2 = 3, 2
print(np.random.rand(dim1, dim2)) 
# 你还可以继续添加dim3等更多

​ 运行结果

[[0.83325372 0.6412798 ]
 [0.32903089 0.41498545]
 [0.18548333 0.27637162]]

np.random.rand()是一种最方便去生成带shape的[0,1)之间取值的Array。还有一种写法可以实现同样目的:np.random.random(),在其中传入一个shape进去。

print(np.random.random([dim1, dim2])

​ 运行结果

[[0.51226037 0.13982879]
 [0.75661115 0.49357135]
 [0.01439348 0.00078817]]
  • np.random.randn(), np.random.randint()

​ 除了生成[0,1)之间的随机数,Numpy还可以生成其他数值,或使用其他生成规则。比如按照标准正态分布去生成。

print(np.random.randn(dim1, dim2))

​ 运行结果

[[-1.74779413 -1.81986096]
 [-0.97003389 -0.19717489]
 [ 1.29311087  1.51706469]]

​ 使用np.random.randint()生成随机整数:

print(np.random.randint(low=-3, high=6, size=10))
#[ 5 -2  5  4 -2 -1  1 -2 -1  5]

给你施加随机

  • np.random.choice()

​ 我们已经可以自动生成一批随机数啦,但是 Numpy 的好用功能可不止有这么一些简单的生成功能哦。它还可以对已有的数据做随机化处理。 比如我想随机从一组数据中选择,我就可以用 np.random.choice()。在做遗传算法的时候, 做基因重组配对,就需要经常使用到这个函数。

data = np.aray([2,1,3,4,6])
print("选一个:", np.random.choice(data))
# 选一个: 3
print("选多个:", np.random.choice(data, size=3))
# 选多个: [2 6 1]
print("不重复地选多个(不放回):", np.random.choice(data, size=3, replace=False))
# 不重复地选多个(不放回): [6 4 2]
print("带权重地选择:", np.random.choice(data, size=10, p=[0,0,0,0.2,0.8]))
# 带权重地选择: [4 6 6 6 6 6 4 6 6 4]
  • np.random.shuffle(), np.random.permutation()

​ choice这个功能真的经常会用到。而在机器学习中,你也许会经常在epoch迭代训练数据的时候,碰到shuffle的概念。如果你在机器学习中没弄懂也没关系,给你补充一下。Numpy里也有np.random.shuffle()的功能,就是用来洗牌的。请注意:它会将源数据重新排列,如果你想保留原数据的话,记得使用np.copy(data)备份一下

data_copy = np.copy(data)
np.random.shuffle(data)
print("源数据:", data_copy)
print("after shuffled:", data)
# 源数据: [2 1 3 4 6]
# shuffled: [6 2 1 3 4]

​ 还有一个功能,np.random.permutation(), 它实现的是 np.random.shuffle() 的一种特殊形式。可以说是一种简单处理特殊情况的功能。 它有两个方便之处,1. 直接生成乱序的序列号,2. 对数据乱序。

而且相比 np.random.shuffle()permutation 有一个好处,就是可以返回一个新数据,对原本的数据没有影响。而且还可以处理多维数据。

print("直接出乱序序列:", np.random.permutation(10))
data = np.arange(12).reshape([6, 2])
print("多维数据在第一维度上乱序:", np.random.permutation(data))

​ 运行结果

直接出乱序序列: [2 0 8 5 7 6 1 9 3 4]
多维数据在第一维度上乱序: [[ 6  7]
 [ 2  3]
 [10 11]
 [ 4  5]
 [ 0  1]
 [ 8  9]]

随机分布

  • np.random.normal(), np.random.uniform()

​ 对于统计学或者机器学习,我们在生成数据的时候,有时需要按照特定的统计学分布来生成,比如需要一个正态分布的抽样数据,或者均匀分布的数据抽样结果。 又或者是其他更高级的,比如泊松分布等等,都可以用 Numpy 来实现。这里我们只介绍一下在机器学习中比较常用的 正态分布 和 均匀分布。

# (均值,方差,size)
print("正态分布:", np.random.normal(1, 0.2, 10))
# (最低,最高,size)
print("均匀分布:", np.random.uniform(-1, 1, 10))

​ 运行结果

正态分布: [0.94567776 1.12786411 1.31172487 1.20797385 0.65761211 1.35564439
 1.02341514 0.89390523 1.34336123 1.13994983]
均匀分布: [-0.15584577  0.49379278 -0.51443373  0.47639447  0.85539253 -0.12520956
  0.38551024  0.92961516  0.43014289  0.54910227]

随机种子的重要性

​ 在机器学习中,我们要对比两种随机初始化模型的优劣,或者在强化学习中要固定随机环境的随机序列,用于复现当前配置的情况,我们通常要做的事情就是伪随机。 简单说,就是每次都是一组随机,但是我可以后续再完整运行一遍一模一样的随机效果。比如生成两遍一模一样的随机序列。

  • np.random.seed()

​ 为了达到这个目的,我们要了解Numpy中的random seed概念,随机种子。当我们把种子固定的时候(用一个数字),同一个种子(数字)产生的随机序列就会一样。

#seed(1) 代表的就是1号随机序列
np.random.seed(1)
print(np.random.rand(2,3))
print(np.random.randint(2,3))

​ 运行结果

[[4.17022005e-01 7.20324493e-01 1.14374817e-04]
 [3.02332573e-01 1.46755891e-01 9.23385948e-02]]
2

​ 此时无论你运行多少次上面的代码,你看到的随机结果,都是同一种结果。当你想改变随机种子的时候,可以在seed()中传入不同的数字。

​ 有时候还可以这么用:在同一次执行代码时,重新设定种子,让随机在同一次执行中复现。

np.random.seed(2)
print(np.random.rand(2))
np.random.seed(2)
print(np.random.rand(2))
#[0.4359949  0.02592623]
# [0.4359949  0.02592623]

​ 对于同一份代码,两次运行都是一样的结果。

总结

​ 你不光可以利用 Numpy 来创建很多种不同的随机数,还能对数据做随机化处理,甚至还能当上帝,控制计算机的随机过程(seed),让电脑可以复现你的随机过程。

对速度有洁癖?快来了解Numpy的View与Copy

​ 通过这节内容理解Numpy的底层逻辑,在这其中有一个非常重要的概念:那就是View和Copy,你会发现,有可能前几天要花10天处理完的数据,学完这个之后,一优化,只需要1小时就搞定了。

如果你对 Numpy 运算速度有追求,我十分建议你了解接下来的内容。如果你是萌新, 目前阶段不用 Numpy 处理大数据(上百MB 的文件),那下面的内容你可以以后再作了解。

Numpy Array和Python List内部结构差别

以后再学!该开始Pandas了噢!