数据处理:python for data science学习记录

卓新知
2023-12-01

可以在线获取Python Data Scinece Handbook, 章节打开的速度比较慢,可以看一小节的时候,loading另外一个小节。

Chapter 2 Understanding numpy[认识numpy模块在数据处理中的重要性]

numpy的基本知识点(nuts and bolts)

  1. 动态创建更耗费内存
    在第一小节中介绍到python这种动态编程语言因为允许不用声明而创建变量,所以每一个创建的变量都带有变量的类型、存储地址及大小信息,相比较其他静态语言例如C就会需要更大的计算资源。所以在python中创建的一个变量其实是一个指针,指向了一个数据结构。
  2. 在生产或者线上代码中使用固定类型的数据结构会减少存储代价。因为在实现层面上,list是一个指向了多个指针的结构,而固定类型的数据结构例如numpy类型的array却是一个指针,指向一个连续的数据块。所以在基于array进行数据计算的时候,可以减少type checking等带来的时间代价。
  3. 在创建多维array的时候,可以使用下面的代码:
np.array([range(i , i+3) for i in [1,2,3]])
  1. 同时如果需要可以通过np.zeros, ones, linspace, random,eye, arrange创建具有固定填充规则的数据结构。
    numpy.array has several useful attributes such as dtype, ndim, shape, size.
  2. array的indexing和slicing并不相同,前者是获取其中的一个element,后者是获得array的一些截片。语法上,都是通过[]实现。在indexing中通过逗号分隔每个维度,例如三维的array x1, x1[1,2,3]是获取一个元素;x1[::, ::, ::]是slicing的语法。在slicing的语法中,x[start:stop:step, start:stop:step]意思指by default start = 0, stop = size of dimension, step =1, 当step = negative number的时候表示acessing是从后往前,默认将start和stop的值进行了替换。start,stop,step是可以不赋值,使用默认值替代。有多少个dimension,就通过ndim-1个逗号分隔想要获取的维度中的index。有一种特殊的情况是需要一行或者一列,这个时候可以使用[:, 0] # 选择第一列,[0, :]选择第一行,同时为了语法上的间接,可以通过[0]来表示[0, :]. 需要注意的是slice操作获取的仅仅是之前array的view,而非copy,这个特性不同于list。
b = a[::]
print(b)
b[0] = 4
print(a, b)

在上面的代码中,b的元素被修改了,而a却保留了原始值。

x11 = x1[0, 0, ::]
print(x11)
x11 = np.array([0,0,0])
print(x11)
print(x1)

这里x1的值就被x11的value reset所修改了。

  1. 如果需要copy一个array,可以通过使用x2 = x1.copy()实现。另外reshape函数则会将一个array通过no-copy的方式进行shape的转换。
x0 = np.arange(1, 10)
x1 = x0.reshape((3,3)) # 注意括号的嵌套
x1.ndim
x1[0,0] = 100
print(x0, x1) # x0的第一个元素已经被修改了

x0并没有因为reshapeg改变维度的个数。但是改变x1的值会相应的改变x0的值。
7. 当需要进行array叠加的时候可以使用concatenate函数,或者vstack和hstack,concatenate通过axis(zero-indexed)控制叠加的方式,axis = 0 相当于vstack(v stands for vertically,顾名思义是垂直叠加),axis = 1 相当于hstack(h stands for horizontally,平行叠加)。通过stack获取的结果变量都是重新create。相对的,split、vsplit、hsplit函数是将一个array按照给定的分割点进行分割。

a = np.array(range(1, 10))
b = np.array(range(2, 11))
c = np.hstack([a,b])
d = np.vstack([a, b])
print(c)
print(d)
# np.hsplit(c, [9])
left, right = np.vsplit(d, [1])

np.split()函数可以对多维数组进行切割,第一个参数是array本身,第二个是integer或者是一个一维的array,当参数为integer时表示将当前array均分成n个子数组,如何分割通过axis进行控制,如果无法进行均等分割的时候,函数会报错。如果参数是一维数组,表示分割点,若超过index,会返回空的subarray。

为什numpy在python的data science领域如此的重要

  1. python提供了universal functions(ufuncs)实现optimized computation with array of data. 下面就会介绍使用普遍的计算ufuncs。
  2. ufuncs一般有两种类型,一种是作用于一个元素,另一种针对两个元素操作。而且numpy的ufuncs使用非常的natural,因为它采用了python最原始的操作符号。每一个符号都是对np函数的一个wrapper,例如“+”是对np.add()的wrapper。
# numpy的算术操作
a = np.arange(-1, 10,  1)
print(a + 5)
print(a / 5)
print(a ** 2)
print(2 ** np.abs(a)) # 因为a是int,而非float, 所以指数只能是正数
print(a // 10)
print(a - 5)
print(np.abs(a))
print(a % 2)
print(a ** 2 + a + 1)
print(np.exp2(a)) # 指数可以是negative number
# print(np.power(2, a)) # Integers to negative integer powers are not allowed,这是因为a是int,而非float导致的。
# 这都是因为python底层实现导致的,一般而言,np操作的输出数据类型依据的是输入的dtype而不是 output的value,当输入是int,而输出是float的时候
# 底层实现时候选择报错,而没有将结果强制转换为float,which is acceptable.
# 如果将array设置为float类型,则指数为negative number便是可以允许的了
# https://stackoverflow.com/questions/43287311/why-cant-i-raise-to-a-negative-power-in-numpy 有更加详细的解释
# 同时有必要了解下numpy里面的变量类型:https://docs.scipy.org/doc/numpy-1.13.0/user/basics.types.html
# uint8和int8的区别是前者是正整数,后者可以是-整数,同理与uint16\uint32\uint64,位数的不同决定了值的范围。
b = np.arange(-1, 10) # 但是如果增加dtype = 'float',np.power(2, b)可以正常输出
print(np.power(2, b)) # Integers to negative integer powers are not allowed.
print(2 ** b) 
print(np.power(-3, np.abs(a)))

print(np.log(a)) # base is e
print(np.log2(np.abs(a)))
print(np.log10(np.abs(a)))

# sometimes 为了保留精度,使用了特殊version的函数来替代raw np.exp and np.log
x = [0, 0.001, 0.01, 0.1]
print("exp(x) - 1 =", np.expm1(x))
print("log(1 + x) =", np.log1p(x))

# 还有很多统计学里面的function可以在scipy.special和numpy里面找到,just search them in the munual book online

对应的output

[ 4  5  6  7  8  9 10 11 12 13 14]
[-0.2  0.   0.2  0.4  0.6  0.8  1.   1.2  1.4  1.6  1.8]
[ 1  0  1  4  9 16 25 36 49 64 81]
[  2   1   2   4   8  16  32  64 128 256 512]
[-1  0  0  0  0  0  0  0  0  0  0]
[-6 -5 -4 -3 -2 -1  0  1  2  3  4]
[1 0 1 2 3 4 5 6 7 8 9]
[1 0 1 0 1 0 1 0 1 0 1]
[ 1  1  3  7 13 21 31 43 57 73 91]
[5.00e-01 1.00e+00 2.00e+00 4.00e+00 8.00e+00 1.60e+01 3.20e+01 6.40e+01
 1.28e+02 2.56e+02 5.12e+02]
[5.00e-01 1.00e+00 2.00e+00 4.00e+00 8.00e+00 1.60e+01 3.20e+01 6.40e+01
 1.28e+02 2.56e+02 5.12e+02]
[5.00e-01 1.00e+00 2.00e+00 4.00e+00 8.00e+00 1.60e+01 3.20e+01 6.40e+01
 1.28e+02 2.56e+02 5.12e+02]
[    -3      1     -3      9    -27     81   -243    729  -2187   6561
 -19683]
[       nan       -inf 0.         0.69314718 1.09861229 1.38629436
 1.60943791 1.79175947 1.94591015 2.07944154 2.19722458]
[0.               -inf 0.         1.         1.5849625  2.
 2.32192809 2.5849625  2.80735492 3.         3.169925  ]
[0.               -inf 0.         0.30103    0.47712125 0.60205999
 0.69897    0.77815125 0.84509804 0.90308999 0.95424251]
exp(x) - 1 = [0.         0.0010005  0.01005017 0.10517092]
log(1 + x) = [0.         0.0009995  0.00995033 0.09531018]
/Users/jiawei/opt/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:25: RuntimeWarning: divide by zero encountered in log
/Users/jiawei/opt/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:25: RuntimeWarning: invalid value encountered in log
/Users/jiawei/opt/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:26: RuntimeWarning: divide by zero encountered in log2
/Users/jiawei/opt/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:27: RuntimeWarning: divide by zero encountered in log10
  1. 为了节省memory的开销,在numpy的每个一个ufuncs都可以通过out制定计算需要输出的变量,否则系统会现建一个新的临时变量,再将这个临时变量赋给等号左边的变量。
	x = np.arange(1, 10, 2)
	y = np.empty(len(x))
	np.add(x , x, out = y)
	print(y)
	z = np.zeros(10)
	np.add(x , x, out = z[::2])
	print(z)
  1. 在numpy中关于binary的操作函数,例如add、subtract、multiply、mod、floor_divide、didive、power 实现了非常有意思的aggregate操作,可以通过reduce函数对一个array每个元素进行连续的操作,前面元素的结果作为一个参与者,后面一个元素作为另外一个参与者,同时可以使用accumulate将整个计算过程存储成一个array。这些聚集的操作并不会影响原来array的结果,而是重新创建了一个array。
a = np.arange(1, 10, 1)
print(a)
np.add.reduce(a)
np.add.accumulate(a)

a = np.arange(2, 10, 1, dtype = 'float')
np.divide.reduce(a)
np.divide.accumulate(a)

np.subtract.reduce(a)
np.subtract.accumulate(a)

np.floor_divide.reduce(a)
np.floor_divide.accumulate(a)

np.power.reduce(a)
np.power.accumulate(a)

np.mod.reduce(a)
np.mod.accumulate(a)
print(a)

-- output
[1 2 3 4 5 6 7 8 9]
[2. 3. 4. 5. 6. 7. 8. 9.]
  1. Aggregation function of numpy is much faster than aggregation function in python.
    在数据分析中,第一步往往是通过观察样本的统计指标(typical values)来了解数据。直接使用基于编译后代码的numpy aggregation function可以更快的观察到这些数据。所以
    Whenever possible, make sure that you are using the NumPy version of these aggregates when operating on NumPy arrays!
big_data_array = np.arange(1, 100000, 1)
%time sum(big_data_array)
%time np.sum(big_data_array)
# CPU times: user 18.6 ms, sys: 480 µs, total: 19.1 ms
# Wall time: 19.3 ms
# CPU times: user 237 µs, sys: 69 µs, total: 306 µs
# Wall time: 178 µs
%time np.std(big_data_array)
%time np.mean(big_data_array)
%time np.var(big_data_array)
%time np.max(big_data_array)
%time np.min(big_data_array)
%time np.percentile(big_data_array, 25)
# 也可以使用简介版本的syntax,如下:
big_data_array.max()
big_data_array.min()
big_data_array.mean()
big_data_array.std()
big_data_array.var()
big_data_array.sum()

在一些array中可能出现NaN的值,这个时候numpy 1.8之后提供了各种聚集函数的NaN-safe counterpart while ignoring missing values.

# nanargmin and nanargmax are the couterparts of argmin and argmax for the safty of NaN values in the array
np.nanargmax(big_data_array)
np.nanargmin(big_data_array)
np.any(big_data_array)
np.all(big_data_array)

可以通过pandas直接read file,然后转换成array进行处理,不过不知道是否比利用pandas的describe函数要快一些。

import pandas as pd
data = pd.read_csv('data/president_heights.csv')
heights = np.array(data['height(cm)'])
print(heights)
  1. 所谓的fancy indexing是指除去通过slicing的方式access想要的元素外,还可以通过传送index的array来获取特定的元素。而这种acessing在list中是不work的。
l = [1,2,3]
ind = [1,3]
l[ind]
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-162-503a25cf5ab2> in <module>
      1 l = [1,2,3]
      2 ind = [1,3]
----> 3 l[ind]

TypeError: list indices must be integers or slices, not list
a = np.linspace(-2, 2, 5)
b = np.ones((5))
c = np.multiply.outer(a, b)
print(c[:, 0])
ind = [1,2,3]
print(c[ind])

## output
[-2. -1.  0.  1.  2.]
[[-1. -1. -1. -1. -1.]
 [ 0.  0.  0.  0.  0.]
 [ 1.  1.  1.  1.  1.]]
numpy.outer(a, b, out=None)[source] Compute the outer product of two vectors.
Parameters:	
a : (M,) array_like
First input vector. Input is flattened if not already 1-dimensional.
b : (N,) array_like
Second input vector. Input is flattened if not already 1-dimensional.
out : (M, N) ndarray, optional
A location where the result is stored
New in version 1.9.0.
Returns:	
out : (M, N) ndarray
out[i, j] = a[i] * b[j]

fancy indexing可以传二维的数组,而且神奇的是返回数组的shape和传输ind的shape保持一致,而不是原数组。这应该都是和设计这些操作的目的有关系,本身使用这些函数的user就是希望能够取得对应的结果,所以没有必要和原数组的shape保持一致。

rand = np.random.RandomState(0)
x = rand.randint(1000, size = 10)
print(x)
ind = [1,2,3]
print(x[ind])
ind = np.arange(1, 5).reshape(2,2)
print(x[ind])

## output
[684 559 629 192 835 763 707 359   9 723]
[559 629 192] # 原数组是一维,ind也是一维,输出也是一维
[[559 629]  # 原数组是一维,ind是二维,输出随着ind的维数变成了二维
 [192 835]]
# 关于arange函数,arange(start, end, stop), start和stop可以省略,start默认从0开始,且0 included,step默认是1;
# 如果specify了step那么start就必须要specify。stop是not included in the interval

那fancy indexing如何作用于多维数组呢?

x = np.arange(12).reshape(3, 4)
row = np.array([0, 1, 2])
col = np.array([2, 1, 3])
print('x is shown as follows')
print(x)
print('fancy indexed result:')
print(x[row, col])
# output
x is shown as follows
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
fancy indexed result:
[ 2  5 11]

row和col的维度都是1,所以经过fancy indexing的结果array也是一维的。
**The pairing of indices in fancy indexing follows all the broadcasting rules that were mentioned in Computation on Arrays: Broadcasting. **
在上面的case中,row和col中的元素是一一对应的,但如果在维度不同的case下,就需要了解numpy中的broadcasting的规则。

Important array broadcasting in numpy

在前面我们提到可以使用ufunc快速实现numpy的数值计算和统计,从而减少使用无效的loop。broadcasting就是numpy所设计的一组规则用于处理计算时两个array具有不同size的情况。
broadcasting具体的规则

  • Rule1 : 首先比较两个array的维度是否一致,如果不是一致,就将维度小的那个array的shape进行填充1,且从左边开始填充。例如一个array的shpape是(3,1) another one is (3,), 则对后者padding成(1,3)。同理于(3,1,1)和(3,),将后者通过两次padding最后成为(1,1,3)。需要注意的是np.arange(n)得到的array是一维,但是其shape是n,所以其shape属性是(n,),通过这个case就可以大致区分维度和shape的不同。
  • Rule2:在Rule1被满足后,就需要比较两个array的shape,观察是否存在shape有1的case,如果有1,则将这个数组shape为1的dimenstion进行stretch,符合计算的规则,和两外一个array的shape所符合。
  • Rule3: 假设两个array在任何一个dimension上的size都不一样,会raise error。同理,在参与计算的array不存在size为1的情况,也不需要因为两者维数不一样而进行padding,那么会raise error,发现通过1和2也没有办法保持两个数组shape符合计算规则,则会raise error。

假设我们通过newaxis为row增加了一个维度,则col也需要通过broadcasting的规则对应的进行扩展,最后选择原数组中的元素。

x = np.arange(12).reshape(3, 4)
row = np.array([0, 1, 2])
col = np.array([2, 1, 3])
print('x is shown as follows')
print(x)
print('fancy indexed result:')
print(x[row, col])

y = x[row[:, np.newaxis], col]
print(y)
print('after adding new axis to the row array')
print(x[row[:, np.newaxis], col])
#output
x is shown as follows
[[ 0  1  2  3]
[ 4  5  6  7]
[ 8  9 10 11]]
fancy indexed result:
[ 2  5 11]
[[ 2  1  3]
[ 6  5  7]
[10  9 11]]
after adding new axis to the row array
[[ 2  1  3]
[ 6  5  7]
[10  9 11]]

Boolean masks

在work with numpy array时候,我们需要对array进行满足某些条件的分析,例如个数、均值等,甚至是从numpy array中删除一些outliers。Boolean masking可以非常好的完成这些操作。
在numpy中,不仅仅有+,、,*,/这样的ufuncs,而且还实现了<(less than), > (greater than), less than or equal, greater than or equal, not equal(!=), equal(==),这些可以用于element-wise处理但却非常快的ufuncs。这些操作符返回的结果是一个boolen的array(这点很重要,返回结果是布尔数组),as follow examples show:

import numpy as np
a  = np.linspace(-1, 10, 10)
print(a)
print(a <  2)
print(a <= 2)
print(a != 2)
print(a ==  2)
# Try these comparion opertions on array of multipy dimensions
b = a.reshape(2, 5)
print(b)
# It also works.
print(b <= 2)

对应的output

[-1.          0.22222222  1.44444444  2.66666667  3.88888889  5.11111111
  6.33333333  7.55555556  8.77777778 10.        ]
[ True  True  True False False False False False False False]
[ True  True  True False False False False False False False]
[ True  True  True  True  True  True  True  True  True  True]
[False False False False False False False False False False]
[[-1.          0.22222222  1.44444444  2.66666667  3.88888889]
 [ 5.11111111  6.33333333  7.55555556  8.77777778 10.        ]]
[[ True  True  True False False]
 [False False False False False]]

当你获得了这些boolen result,仅仅是第一步,numpy提供了很多方便的函数去统计分析这些布尔结果。 那才是你真正想要的。

print(np.sum(a < 2))
print(np.sum(b < 2, axis = 0)) # This counts the number of values less than 2 in each column of the matrix.
print(np.sum(b < 2, axis = 1)) # This counts the number of values less than 2 in each row of the matrix.
# output
3
[1 1 1 0 0]
[3 0]

如果对具体的满足条件的数值不感兴趣,可以使用np.any, np.all来确认是否任何一个或者所有的element都符合你的选择条件。同样,any和all函数也都可以通过axis参数来指定是以每行为单元或每列为单元。

print(np.all(a < 2))
print(np.any(a < 2))
print(np.all(b < 2, axis = 0))
print(np.any(b < 2, axis = 1))
# corresponding results
False
True
[False False False False False]
[ True False]

在上面介绍了单一的选择条件如何实现,而混合选择条件则是现实生活中最常用到的功能。numpy 通过位操作符来达到目的:|, &, ^, ~。这些操作符,numpy重载之后经常用于boolean arrays。^ 是xor,~是非逻辑运算符。
Like with the standard arithmetic operators, NumPy overloads these as ufuncs which work element-wise on (usually Boolean) arrays.

print(np.sum((a < 2) & (a > 100)))
print(np.count_nonzero((a < 2) & (a > 100)))

**注意小括号是必要的,否则结果并不会是你想要的。**因为操作符优先级的问题。
OK,now我们知道如果对这些boolen结果进行统计,那如何从data中选出对应的数据并进行统计分析,使用之前介绍的medain、sum等聚集函数。Let’s look at it. A more powerful pattern is to use Boolean arrays as masks, to select particular subsets of the data themselves.

days = np.arange(365)
summer = (days > 172) & (days < 262)
inches = np.linspace(0, 365, 365)
inche_subset = inches[summer]
np.median(inche_subset)
np.sum(inche_subset)

所以通过boolean operations,mask operations,aggregates operations,可以对手头的数据进行多样的统计分析。经常容易困户的是and opertor and or operator, which are different from & and |。
前者是整个evaluate参与比较的变量,在python中所有非零的数值都是true,所以bool(1 and -10)的结果就是True。但是bitwise操作符号,bool(1 & -10)的结果就是False。

a = np.array([1, 0, 1], dtype = bool)
b = np.array([1, 0, 1], dtype = bool)
a & b # 正常运行
a and b # 会报错

所以在处理boolean array的时候,&和|是经常适合的操作。

 类似资料: