# 创建元组最简单的办法就是用逗号分隔序列值
tup = 4,5,6
tup
nested_tup = (4,5,6), (7,8)
nested_tup
# 使用tuple函数将任意序列或迭代器转换为元组:
tuple([4,0,2])
tup = tuple('string')
tup
# 元组的元素可以通过[]来获取
tup[0]
# 对象元组中存储的对象其自身是可变的,但是元组一旦创建,各个位置上的对象是无法被修改的
tup = tuple(['foo', [1,2], True])
tup[2] = False
# 如果元组中一个对象是可变的,例如列表,那么在它内部进行修改
tup[1].append(3)
tup
# 可以使用+号连接元组来生成更长的元组
(4, None, 'foo') + (6,0) + ('bar', )
# 将元组乘以整数,则会和列表一样,生成含有多分拷贝的元组
('foo', 'bar') *4
tup = (4,5,6)
a, b, c= tup
b
tup = 4,5,(6,7)
a, b, (c, d) = tup
d
# 拆包的一个常用场景就是遍历元组或列表组成的序列
seq = [(1,2,3), ( 4,5,6), (7,8,9)]
for a, b, c in seq:
print('a={0}, b={1}, c{2}'.format(a, b, c))
values = 1,2,3,4,5
a, b, *rest = values
a, b
rest
rest部分有时是想要丢弃的数据,rest这个变量并没有特殊之处,为了方便,很多Python编程者会使用下划线(_)来表示不想要的变量
a, b, *_ = values
a = 1,2,2,2,3,4,2
a.count(2) # 计算2在a中出现的次数
a_list = [2,3,4,None]
tup = ('foo', 'bar', 'baz')
b_list = list(tup)
b_
gen = range(10)
gen
list(gen)
b_list.append('dwarf') # 添加元素到列表的末尾
b_list
b_list.insert(1, 'red') # 在指定位置插入元素
b_list
insert
与append
相比,计算代价更高,因为子序列元素不得不在内部移动为新元素提供空间。如果你想要在序列的头部和尾部都插入元素,那你应该探索下collections.deque
,它是一个双端队列,可以满足头尾部都增加的要求。
insert
的反操作就是pop
,其将特定位置的元素移除并返回
b_list.pop(2)
b_list
# 元素可以通过remove方法移除,该方法会定位第一个符合要求的值并移除它
b_list.append('foo')
b_
b_list.remove('foo')
b_list
'dwarf' in b_list
'dwarf' not in b_list
[4, None, 'foo'] + [7, 8, (2,3)]
# 如果有一个已经定义的列表,可以用extend向该列表添加多个元素
x = [4, None, 'foo']
x.extend([7,8,(2,3)])
注意,通过添加内容来连接列表是一种相对高价的操作,这是因为连接过程中创建了新列表,并且还要复制对象。使用extend
将元素添加到已经存在的列表是更好的方式,尤其是在创建一个大型列表时:
# 更快的方法
everything = []
for chunk in list_of_lists:
everything.extend(chunk)
# 更慢的方法
everything = []
for chunk in list_of_lists:
everything = everything + chunk
a = [7, 2, 5, 1, 3]
a.sort()
a
sort
有一些选项偶尔会派上用场,其中一项是传递一个二级排序key——一个用于生成排序值的函数,例如,我们可以通过字符串的长度进行排序:
b = ['saw', 'small', 'He', 'foxes', 'six']
b.sort(key=len)
some_list = ['foo', 'bar', 'baz']
mapping = {}
for i, v in enumerate(some_list):
mapping[v] = i
mapping
seq1 = ['foo', 'bar', 'baz']
seq2 = ['one', 'two', 'three']
zipped = zip(seq1, seq2)
list(zipped)
for i , (a, b) in enumerate(zip(seq1, seq2)):
print('{0}:{1}, {2}'.format(i, a, b))
给定一个已“配对”的序列时,zip
函数有一种机智的方式去“拆分”序列。这种方式的另一种思路就是将行的列表转换为列的列表
pitchers = [('Nolan', 'Ryan'), ('Roger', 'Clemens'), ('Schilling', 'Curt')]
first_names, last_names = zip(*pitchers)
fir
last_names
d1 = {'a':'some value', 'b':'[1,2,3,4]', 7:'an integer'}
d1
d1['b']
d1[5] = 'some value'
d1['dummy'] = 'another value'
d1
# 可以使用del关键字或pop方法删除值,pop方法会在删除的同时返回被删的值,并删除键
del d1[5]
d1
d1.pop("dummy")
d1
d1.keys()
d1.values
# 使用update合并两个字典
d1.update({'b':'foo', 'c':12})
d1
mapping = {}
for key, value in zip(key_list, value_list):
mapping[key] = value
由于字典本质上是2元组的集合,所以字典可以接受一个2元组的列表作为参数
mapping = dict(zip(range(5), reversed(range(5))))
map
words = ['apple', 'bat', 'bar', 'atom', 'book']
by_letter = {}
for word in words:
letter = word[0]
if letter not in by_letter:
by_letter[letter] = [word]
else:
by_letter[letter].append(word)
by_letter
# 字典的setdefault方法就是为了这个目的产生的,上述的for循环可以写成:
for word in words:
letter = word[0]
by_letter.setdefault(letter, []).append(word)
set([2,2,2,1,3,3])
{2,2,2,1,3,3}
# 集合支持数学上的集合操作,例如联合、交集、差集、对称差集
a = {1,2,3,4,5}
b = {3,4,5,6,7,8}
a.union(b) # 两个集合的联合
a | b # 两个集合的联合
a.intersection(b) # 交集
a & b # 交集
a.add(x)
将元素x加入结合a
a.clear()
将集合重置为空,清空所有元素
a.remove(x)
从集合a移除某个元素
a.pop()
移除任意元素,如果集合是空的抛出keyError
a.union(b)
a | b
a和b中的所有不同元素
a.update(b)
a |= b
将a的内容设置为a和b的并集
a.intersection(b)
a&b
a和b同时包含的元素
a.intersection_update(b)
a &= b
将a内容设置为a和b的交集
a.difference(b)
a-b
在a不在b的元素
a.difference_update(b)
a-=b
将a的内容设为在a不在b的元素
a.symmetric_difference(b)
a^b
所有在a或b中,但不是同时在a、b中的元素
a.symmetric_difference_update(b)
a^=b
将a的内容设为所有在a或b中,但不是同时在a、b中的元素
a.issubset(b)
如果a包含于b返回为True
strings = ['a', 'as', 'bat', 'car', 'dove', 'python']
[x.upper() for x in strings if len(x) > 2]
# 字典推导式
dict_comp = {key-expr : value-expr for value in collection if condition}
# 集合推导式
unique_lengths = {len(x) for x in strings}
unique
# 使用map函数更函数化、更简洁
set(map(len, strings))
loc_mapping = {val : index for index, val in enumerate(strings)}
loc_
# 嵌套推导式
all_data = [['John', 'Emily', 'Michael', 'Mary', 'Steven'], ['Maria', 'Juan', 'Javier', 'Natalia', 'Pilar']]
names_of_interest = []
for names in all_data:
enough_es = [name for name in names if name.count('e') >= 2]
names_of_interest.extend(enough_es)
names_of_interest
# 等价于
result = [name for names in all_data for name in names if name.count('e') >=2 ]
resu
使用生成器表达式来创建生成器
gen = (x**2 for x in range(100))
# 上面的生成器代码与下面更复杂的生成器代码式等价的
def _make_gen():
for x in range(100):
yield x**2
gen = _make_gen()
# 在很多情况下,生成器表达式可以作为函数参数用于替代列表推导式
sum(x**2 for x in range(100))
dict((i, i**2) for i in range(5))
标准库中的itertools
模块是适用于大多数数据算法的生成器集合。例如,groupby可以根据任意的序列和一个函数,通过函数的返回值对序列中连续的元素进行分组
import itertools
first_letter = lambda x:x[0]
names = ['Alan', 'Adam', 'Wes', 'Will', 'Albert', 'Steve']
for letter, names in itertools.groupby(names, first_letter):
print(letter, list(names)) # names is a generator
一些常用的itertools函数
combinations(iterable, k)
根据iterable参数中的所有元素生成一个包含所有可能K-元组的序列,忽略元素的顺序,也不进行替代(需要替代用 `combinations_with_replacement)
permulations(iterable, k)
根据iteratble参数中的所有元素按顺序生成包含所有可能k元组的序列
import numpy as np
my_arr = np.arange(1000000)
my_list = list(range(1000000))
%time for _ in range(10): my_arr2 = my_arr*
%time for _ in range(10): mylist2 = [x * 2 for x in my_list]
data = np.random.randn(2,3)
dat
data
data+dat
data
dat
生成数组最简单的方式就是使用array函数。array函数接收任意的序列型对象(当然包括其他的数组),生成一个新的包含传递数据的NumPy数组。
data1 = [6,7.5, 8, 0,1]
arr1 = np.array(data1)
arr
# 嵌套序列,例如同等长度的列表,将会自动转换成多维数组
data2 = [[1,2,3,4], [5,6,7,8]]
arr2 = np.array(data2)
arr
arr2.ndim
arr2.sh
np.zeros(10)
np.zeros((3,6))
np.empty((2,3,2))
# arange是Python内建函数range的数组版
np.arange(15
np.ones
np.ones_like
根据所给的数组生成一个形状一样的全1数组
np.full
根据给定的形状和数据类型生成制定数值的数组
np.full_like
根据所给的数组生成一个形状一样但内容是指定数值的数组
eye, identity
生成一个NxN特征矩阵(对角线都是1,其余位置是0)
NumPy 数据类型
np.int8, np.uint8
:i1, u1
np.int16, np.uint16
:i2, u2
np.int32, np.uint32
:i4, u4
np.int64, np.uint64
:i8, u8
np.float16
: f2
np.float32
: f4
或f
np.float64
: f8
或d
np.float128
: f16
或g
np.complex64
: c8, c16, 32
分别基于32位、64位、128位浮点数的复数
np.complex128
np.complex256
np.bool
: ?
np.object
: o
np.string_
: s
修正的ASCII字符串类型,例如生成一个长度为10的字符串类型,使用'S10'
np.unicode
: U
修正的Uniconde类型,生成一个长度为10的Unicode类型
# 使用astype方法显式的转换数组的数据类型
arr = np.array([1,2,3,4,5])
arr.dtype
float_arr = arr.astype(np.float64)
float_arr.dty
arr = np.array([3.7, -1.2, -2.6, 0.5, 12.9, 10.1])
arr
arr.astype(np.int32)
# 如果有一个数组,里面的元素都是表达数字含义的字符串,也可以通过astype将字符串转换为数字
numeric_strings = np.array(['1.25', '-9.6', '42'], dtype=np.string_)
numeric_strings.astype(float)
在Numpy中,当使用np.string_
类型做字符串数据要小心,因为NumPy会修改它的大小或删除输入且不发出告警。pandas在处理数据时有更直观的开箱型操作。
数组之所以重要是因为它允许你进行批量操作而无须任何for循环。这种特性称为向量化。任何在两个等尺寸数组之间的算术操作都应用了逐元素操作的方式:
arr = np.array([[1.,2.,3.],[4.,5.,6.]])
arr * arr
np.dot(arr,arr.)
arr2 = np.array([[0.,4.,1.],[7.,2.,12.]])
arr
arr2 > ar
arr = np.arange(10)
arr
arr[5]
arr[5:8]
arr[5:8]=12
arr
如上,如果你传入了一个数值给数组的切片,例如arr[5:8]=12
,数值被传递给了整个切片。区别于Python的内建列表,数组的切片是原数组的视图。这意味着并不是被复制了,任何对于视图的修改都会反映到原数组上。
arr_slice = arr[5:8]
arr_slice
arr_slice[1] = 12345
# 不写切片值的[:]将会引用数组的所有值
arr_slice[:] = 64
ar
如果你想要一份数组切片的拷贝而不是一份视图的话,你必须显式的复制这个数组,例如arr[5:8].copy()
arr2d = np.array([[1,2,3],[4,5,6],[7,8,9]])
arr2d[2]
arr2d[0][2]
arr2d[0,2]
arr3d = np.array([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]])
ar
arr3d[0]
old_values = arr3d[0].copy()
arr3d[0] = 42
arr3d
arr3d[0] = old_values
arr3d
arr2d
arr2d[:2, 1:]
names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])
data = np.random.randn(7,4)
names
data
names = 'Bob'
data[names == 'Bob'] # 等同于data[~(names != 'Bob')]
data[names == 'Bob', 2:]
data[names == 'Bob', 3]
names != 'Bob'
data[~(names == 'Bob')]
# 当要选择三个名字中的两个时,可以对多个布尔值条件进行联合,需要使用数学操作符&和|
mask = (names == 'Bob') | (names == 'Will')
data[mask]
使用布尔值索引选择数据i时,总是生成数据的拷贝,即使返回的数组并没有任何变化。Python的关键字and
和or
对布尔值数组并没有用,请使用&
和|
来代替
# 将data中所有的负值设为0
data[data<0] =0
data
神奇索引是NumPy中的术语,用于描述使用整数数组进行数据索引。
arr = np.empty((8,4))
for i in range(8):
arr[i] = i
# 为了选出一个符合特定顺序的子集,可以简单的通过传递一个包含指明所需顺序的列表或数组来完成
arr[[4,3,0,6]]
# 如果使用负的索引,将从尾部进行选择
arr[[-3, -5, -7]]
# 传递多个索引数组时情况有些不同,这样会根据每个索引元组对应的元素选出一个一维数组
arr = np.arange(32).reshape((8,4))
arr
arr[[1,5,7,2], [0,3,1,2]]
arr[[1,5,7,2]][:,[0,3,1,2]]
import numpy as n
arr = np.arange(15).reshape((3,5))
arr
arr.T
arr = np.random.randn(6,3)
arr
np.dot(arr.T, arr)
对于更高维度的数组,transpose
方法可以接受包含轴编号的元组,用于置换轴:
arr = np.arange(16).reshape((2,2,4))
arr
下面,轴已经被重新排序,使得原先的第二个轴变为第一个,原先的第一个变成第二个,最后一个轴并没有改变。
arr.transpose((1,0,2))
使用.T
进行转置是换轴的一个特殊案例。ndarray有一个swapaxes
方法,该方法接收一对轴编号作为参数,并对轴进行调整用于重组数据
arr
arr.swapaxes(1,2) # swapaxes返回的是数据的视图,而没有对数据进行复制
通用函数,也称为ufunc,是一种在ndarray数据中进行逐元素操作的函数。某些简单函数接收一个或多个标量数值,并产生一个或多个标量结果,而通用函数就是对这些简单函数的向量化封装
arr = np.arange(10)
arr
np.sqrt(arr)
np.square(arr)
np.exp(arr)
x = np.random.randn(8)
y = np.random.randn(8)
np.maximum(x, y) # 逐个元素对x和y中元素的最大值计算出来。
# modf是Python内建函数divmod的向量化版本,它返回一个浮点值数组中的小数部分和整数部分
arr = np.random.randn(7) * 5
arr
remainder, whole_part = np.modf(arr)
remainde
whole_part
np.abs(whole_part)
arr
np.exp(arr)
np.log(arr) # np.log10, np.log2, np.log1p
np.sign(arr) # 计算每个元素的符号值,1(正数), 0(0), -1(负数)
np.ceil(arr)
np.floor(arr)
np.rint(arr) # 将元素保留到整数位,并保持dtype
np.isnan(arr)
np.isfinite(arr) # 返回数组中的元素是否有限(非inf,非NaN),是否无限的,形式为布尔值数组
np.isinf(arr)
np.logical_not(np.isinf(arr)) # 对数组的元素按位取反(与~arr效果一致)
其它的一元通用函数还有:cos, cosh,sin, sinh, tan, tanh, arccos, arccosh, arcsin, arcsinh,arctan, arctanh
二元通用函数有:
add
: 将数组的对应元素相加
subtract
: 在第二个数组中,将第一个数组中包含的元素去除
multiply
: 将数组的对应元素相乘
divide, floor_divide
: 除或整除(放弃余数)
power
: 将第二个数组的元素作为第一个数组对应元素的幂次方
maximum, fmax
: 逐个元素计算最大值,fmax忽略NaN
minimum, fmin
: 逐个元素计算最小值,fmin忽略NaN
mod
: 按元素的求模计算(即求除法的余数)
copysign
: 将第一个数组的符号值改为第二个数组的符号值
greater, greater_equal, less, less_equal, equal, not_equal
: >, >=, <, <=, ==, !=
logical_and, logical_or, logical_xor
: &, |, ^
示例:假设对一些网格数据来计算函数sqrt(x^2+y^2)
的值,np.meshgrid
函数接收两个一维数组,并根据两个数组的所有(x, y)对生成一个二维矩阵:
points = np.arange(-5, 5, 0.01) # 1000 equally spaced points
xs, ys = np.meshgrid(points, points)
ys
xs
z = np.sqrt(xs**2 + ys**2)
z
import matplotlib.pyplot as plt
plt.imshow(z, cmap = plt.cm.gray)
plt.colorbar()
plt.title("Image plot of $\sqrt{x^2 + y^2}$ for a grid of val uses")
np.where
函数是三元表达式x if condition else y
的向量化版本。
xarr = np.array([1.1, 1.2, 1.3, 1.4, 1.5])
yarr = np.array([2.1, 2.2, 2.3, 2.4, 2.5])
cond = np.array([True, False, True, True, False])
假设cond中元素为True时,取xarr中的对应元素,否则取yarr中对应元素
result = [(x if c else y) for x, y, c in zip(xarr, yarr, cond)]
reuslt
这样会产生多个问题,首先,如果数组很大时,速度会很慢,因为所有工作是通过Python解释器完成,其次当数组是多维时,就无法奏效,而使用np.where
可以非常简单的完成:
result = np.where(cond, xarr, yarr)
resu
arr = np.random.randn(4,4)
a
arr > 0
np.where(arr>0, 2, arr)
arr = np.random.randn(5,4)
arr
arr.mean()
np.mean(arr)
arr.mean(axis=1) # 计算每一列的平均值
arr.sum(axis=0) # 计算每一行的累和
arr.cumsum()
arr = np.array([[0,1,2], [3,4,5], [6,7,8]])
arr
arr.cumsum(axis=0)
arr.cumprod(axis=1)
arr = np.random.randn(100)
(arr > 0 ).sum()
bools = np.array([False, False, True, False])
bools.any()
bools.all()
arr = np.random.randn(6)
arr
arr.sort()
arr
arr = np.random.randm
arr.sort(1)
arr
names = np.array(['Bob', 'Joe', 'Eill', 'Bob', 'Eill', 'Joe', 'Joe'])
np.unique(names)
sorted(set(names))
np.in1d
检查一个数组中的值是否在另外一个数组中,并返回一个布尔值数组
values = np.array([6,0,0,3,2,5,6])
np.in1d(values, [2,3,6])
类似方法还有intersect1d(x,y), union1d(x,y), in1d(x,y), setdiff1d(x, y), setxor1d(x, y)
NumPy可以在硬盘中将数据以文本或二进制文件的形式进行存入硬盘或由硬盘载入。np.save
或np.load
是高效存取硬盘数据的两大工具函数。数组在默认情况下是以未压缩的格式进行存储的,后缀名是.npy
:
import
arr = np.arange(10)
np.save('test_array', arr)
np.load('test_array.npy')
# 将数组作为参数传递给该函数,用于在未压缩文件中保存多个数组
np.savez('test_archive.npz', a = arr, b = arr*2)
arch = np.load('test_archive')
arch
## 伪随机数生成
samples = np.random.normal(size = (4,4))
samples
rng = np.random.RandomState(1234)
rng.randn(10)