元组

In [1]:
# 创建元组最简单的办法就是用逗号分隔序列值
tup = 4,5,6
tup
Out[1]:
(4, 5, 6)
In [3]:
nested_tup = (4,5,6), (7,8)
nested_tup
Out[3]:
((4, 5, 6), (7, 8))
In [4]:
# 使用tuple函数将任意序列或迭代器转换为元组:
tuple([4,0,2])
Out[4]:
(4, 0, 2)
In [5]:
tup = tuple('string')
tup
Out[5]:
('s', 't', 'r', 'i', 'n', 'g')
In [6]:
# 元组的元素可以通过[]来获取
tup[0]
Out[6]:
's'
In [7]:
# 对象元组中存储的对象其自身是可变的,但是元组一旦创建,各个位置上的对象是无法被修改的
tup = tuple(['foo', [1,2], True])
tup[2] = False
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-7-ab16322c240a> in <module>
      1 # 对象元组中存储的对象其自身是可变的,但是元组一旦创建,各个位置上的对象是无法被修改的
      2 tup = tuple(['foo', [1,2], True])
----> 3 tup[2] = False

TypeError: 'tuple' object does not support item assignment
In [8]:
# 如果元组中一个对象是可变的,例如列表,那么在它内部进行修改
tup[1].append(3)
tup
Out[8]:
('foo', [1, 2, 3], True)
In [9]:
# 可以使用+号连接元组来生成更长的元组
(4, None, 'foo') + (6,0) + ('bar', )
Out[9]:
(4, None, 'foo', 6, 0, 'bar')
In [10]:
# 将元组乘以整数,则会和列表一样,生成含有多分拷贝的元组
('foo', 'bar') *4
Out[10]:
('foo', 'bar', 'foo', 'bar', 'foo', 'bar', 'foo', 'bar')

元组拆包

In [11]:
tup = (4,5,6)
a, b, c= tup 
b
Out[11]:
5
In [12]:
tup = 4,5,(6,7)
a, b, (c, d) = tup 
d
Out[12]:
7
In [13]:
# 拆包的一个常用场景就是遍历元组或列表组成的序列
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))
a=1, b=2, c3
a=4, b=5, c6
a=7, b=8, c9
In [14]:
values = 1,2,3,4,5
a, b, *rest = values 
a, b
Out[14]:
(1, 2)
In [15]:
rest
Out[15]:
[3, 4, 5]

rest部分有时是想要丢弃的数据,rest这个变量并没有特殊之处,为了方便,很多Python编程者会使用下划线(_)来表示不想要的变量

In [16]:
a, b, *_ = values 

元组方法

In [17]:
a = 1,2,2,2,3,4,2
a.count(2) # 计算2在a中出现的次数
Out[17]:
4

列表

In [1]:
a_list = [2,3,4,None]
tup = ('foo', 'bar', 'baz')
b_list = list(tup)
b_
Out[1]:
['foo', 'bar', 'baz']
In [3]:
gen = range(10)
gen
Out[3]:
range(0, 10)
In [4]:
list(gen)
Out[4]:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

增加和移除元素

In [5]:
b_list.append('dwarf') # 添加元素到列表的末尾
b_list
Out[5]:
['foo', 'bar', 'baz', 'dwarf']
In [6]:
b_list.insert(1, 'red') # 在指定位置插入元素
b_list
Out[6]:
['foo', 'red', 'bar', 'baz', 'dwarf']

insertappend相比,计算代价更高,因为子序列元素不得不在内部移动为新元素提供空间。如果你想要在序列的头部和尾部都插入元素,那你应该探索下collections.deque,它是一个双端队列,可以满足头尾部都增加的要求。

insert的反操作就是pop,其将特定位置的元素移除并返回

In [7]:
b_list.pop(2)
Out[7]:
'bar'
In [8]:
b_list
Out[8]:
['foo', 'red', 'baz', 'dwarf']
In [9]:
# 元素可以通过remove方法移除,该方法会定位第一个符合要求的值并移除它
b_list.append('foo')
b_
Out[9]:
['foo', 'red', 'baz', 'dwarf', 'foo']
In [10]:
b_list.remove('foo')
b_list
Out[10]:
['red', 'baz', 'dwarf', 'foo']
In [11]:
'dwarf' in b_list
Out[11]:
True
In [12]:
'dwarf' not in b_list
Out[12]:
False

连接和联合列表

In [13]:
[4, None, 'foo'] + [7, 8, (2,3)]
Out[13]:
[4, None, 'foo', 7, 8, (2, 3)]
In [14]:
# 如果有一个已经定义的列表,可以用extend向该列表添加多个元素
x = [4, None, 'foo']
x.extend([7,8,(2,3)])
Out[14]:
[4, None, 'foo', 7, 8, (2, 3)]

注意,通过添加内容来连接列表是一种相对高价的操作,这是因为连接过程中创建了新列表,并且还要复制对象。使用extend将元素添加到已经存在的列表是更好的方式,尤其是在创建一个大型列表时:

In [ ]:
# 更快的方法
everything = []
for chunk in list_of_lists:
    everything.extend(chunk)

# 更慢的方法
everything = []
for chunk in list_of_lists:
    everything = everything + chunk

排序

In [17]:
a = [7, 2, 5, 1, 3]
a.sort()
a
Out[17]:
[1, 2, 3, 5, 7]

sort有一些选项偶尔会派上用场,其中一项是传递一个二级排序key——一个用于生成排序值的函数,例如,我们可以通过字符串的长度进行排序:

In [18]:
b = ['saw', 'small', 'He', 'foxes', 'six']
b.sort(key=len)
Out[18]:
['He', 'saw', 'six', 'small', 'foxes']

内建序列函数

In [19]:
some_list = ['foo', 'bar', 'baz']
mapping = {}
for i, v in enumerate(some_list):
    mapping[v] = i 
mapping
Out[19]:
{'foo': 0, 'bar': 1, 'baz': 2}
In [20]:
seq1 = ['foo', 'bar', 'baz']
seq2 = ['one', 'two', 'three']
zipped = zip(seq1, seq2) 
list(zipped)
Out[20]:
[('foo', 'one'), ('bar', 'two'), ('baz', 'three')]
In [22]:
for i , (a, b) in enumerate(zip(seq1, seq2)):
    print('{0}:{1}, {2}'.format(i, a, b))
0:foo, one
1:bar, two
2:baz, three

给定一个已“配对”的序列时,zip函数有一种机智的方式去“拆分”序列。这种方式的另一种思路就是将行的列表转换为列的列表

In [23]:
pitchers = [('Nolan', 'Ryan'), ('Roger', 'Clemens'), ('Schilling', 'Curt')]
first_names, last_names = zip(*pitchers)
fir
Out[23]:
('Nolan', 'Roger', 'Schilling')
In [24]:
last_names
Out[24]:
('Ryan', 'Clemens', 'Curt')

字典

In [25]:
d1 = {'a':'some value', 'b':'[1,2,3,4]', 7:'an integer'}
In [26]:
d1
Out[26]:
{'a': 'some value', 'b': '[1,2,3,4]', 7: 'an integer'}
In [27]:
d1['b']
Out[27]:
'[1,2,3,4]'
In [28]:
d1[5] = 'some value'
Out[28]:
{'a': 'some value', 'b': '[1,2,3,4]', 7: 'an integer', 5: 'some value'}
In [29]:
d1['dummy'] = 'another value'
d1
Out[29]:
{'a': 'some value',
 'b': '[1,2,3,4]',
 7: 'an integer',
 5: 'some value',
 'dummy': 'another value'}
In [30]:
# 可以使用del关键字或pop方法删除值,pop方法会在删除的同时返回被删的值,并删除键
del d1[5]
In [31]:
d1
Out[31]:
{'a': 'some value',
 'b': '[1,2,3,4]',
 7: 'an integer',
 'dummy': 'another value'}
In [33]:
d1.pop("dummy")
Out[33]:
'another value'
In [34]:
d1
Out[34]:
{'a': 'some value', 'b': '[1,2,3,4]', 7: 'an integer'}
In [35]:
d1.keys()
Out[35]:
dict_keys(['a', 'b', 7])
In [36]:
d1.values
Out[36]:
dict_values(['some value', '[1,2,3,4]', 'an integer'])
In [37]:
# 使用update合并两个字典
d1.update({'b':'foo', 'c':12})
d1
Out[37]:
{'a': 'some value', 'b': 'foo', 7: 'an integer', 'c': 12}

从序列生成字典

In [ ]:
mapping = {}
for key, value in zip(key_list, value_list):
    mapping[key] = value 

由于字典本质上是2元组的集合,所以字典可以接受一个2元组的列表作为参数

In [1]:
mapping = dict(zip(range(5), reversed(range(5))))
map
Out[1]:
{0: 4, 1: 3, 2: 2, 3: 1, 4: 0}
In [2]:
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
Out[2]:
{'a': ['apple', 'atom'], 'b': ['bat', 'bar', 'book']}
In [ ]:
# 字典的setdefault方法就是为了这个目的产生的,上述的for循环可以写成:
for word in words:
    letter = word[0]
    by_letter.setdefault(letter, []).append(word)

集合

集合是一种无序且元素唯一的容器。可以认为集合也像字典,但是只有犍没有值。集合可以有两种创建方式:

  • 通过set函数

  • 用字面值集与大括号的语法

In [3]:
set([2,2,2,1,3,3])
Out[3]:
{1, 2, 3}
In [4]:
{2,2,2,1,3,3}
Out[4]:
{1, 2, 3}
In [5]:
# 集合支持数学上的集合操作,例如联合、交集、差集、对称差集
a = {1,2,3,4,5}
b = {3,4,5,6,7,8}
In [6]:
a.union(b) # 两个集合的联合
Out[6]:
{1, 2, 3, 4, 5, 6, 7, 8}
In [7]:
a | b # 两个集合的联合
Out[7]:
{1, 2, 3, 4, 5, 6, 7, 8}
In [8]:
a.intersection(b) # 交集
Out[8]:
{3, 4, 5}
In [9]:
a & b # 交集
Out[9]:
{3, 4, 5}
  • 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

列表、集合和字典的推导式

In [10]:
strings = ['a', 'as', 'bat', 'car', 'dove', 'python']
[x.upper() for x in strings if len(x) > 2]
Out[10]:
['BAT', 'CAR', 'DOVE', 'PYTHON']
In [ ]:
# 字典推导式
dict_comp = {key-expr : value-expr for value in collection if condition}
In [12]:
# 集合推导式
unique_lengths = {len(x) for x in strings}
unique
Out[12]:
{1, 2, 3, 4, 6}
In [13]:
# 使用map函数更函数化、更简洁
set(map(len, strings))
Out[13]:
{1, 2, 3, 4, 6}
In [14]:
loc_mapping = {val : index for index, val in enumerate(strings)}
loc_
Out[14]:
{'a': 0, 'as': 1, 'bat': 2, 'car': 3, 'dove': 4, 'python': 5}
In [15]:
# 嵌套推导式
all_data = [['John', 'Emily', 'Michael', 'Mary', 'Steven'], ['Maria', 'Juan', 'Javier', 'Natalia', 'Pilar']]
In [17]:
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
Out[17]:
['Steven']
In [18]:
# 等价于
result = [name for names in all_data for name in names if name.count('e') >=2 ]
resu
Out[18]:
['Steven']

生成器

使用生成器表达式来创建生成器

In [19]:
gen = (x**2 for x in range(100))
Out[19]:
<generator object <genexpr> at 0x1038bf318>
In [20]:
# 上面的生成器代码与下面更复杂的生成器代码式等价的
def _make_gen():
    for x in range(100):
        yield x**2 
gen = _make_gen()
In [21]:
# 在很多情况下,生成器表达式可以作为函数参数用于替代列表推导式
sum(x**2 for x in range(100))
Out[21]:
328350
In [23]:
dict((i, i**2) for i in range(5))
Out[23]:
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

itertools模块

标准库中的itertools模块是适用于大多数数据算法的生成器集合。例如,groupby可以根据任意的序列和一个函数,通过函数的返回值对序列中连续的元素进行分组

In [24]:
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
A ['Alan', 'Adam']
W ['Wes', 'Will']
A ['Albert']
S ['Steve']

一些常用的itertools函数

  • combinations(iterable, k) 根据iterable参数中的所有元素生成一个包含所有可能K-元组的序列,忽略元素的顺序,也不进行替代(需要替代用 `combinations_with_replacement)

  • permulations(iterable, k) 根据iteratble参数中的所有元素按顺序生成包含所有可能k元组的序列

Numpy 基础:数组与向量化计算

In [2]:
import numpy as np 
my_arr = np.arange(1000000)
my_list = list(range(1000000))
In [3]:
%time for _ in range(10): my_arr2 = my_arr*
CPU times: user 15.1 ms, sys: 31.6 ms, total: 46.8 ms
Wall time: 46.5 ms
In [4]:
%time for _ in range(10): mylist2 = [x * 2 for x in my_list]
CPU times: user 598 ms, sys: 87.5 ms, total: 686 ms
Wall time: 686 ms

NumPy ndarray: 多维数组对象

In [5]:
data = np.random.randn(2,3)
dat
Out[5]:
array([[-1.20169182, -0.71505509,  0.30317871],
       [ 0.07648508, -1.14143538,  1.17996087]])
In [6]:
data
Out[6]:
array([[-12.01691817,  -7.15055088,   3.03178714],
       [  0.76485083, -11.41435377,  11.79960866]])
In [7]:
data+dat
Out[7]:
array([[-2.40338363, -1.43011018,  0.60635743],
       [ 0.15297017, -2.28287075,  2.35992173]])
In [8]:
data
Out[8]:
(2, 3)
In [9]:
dat
Out[9]:
dtype('float64')

生成ndarray

生成数组最简单的方式就是使用array函数。array函数接收任意的序列型对象(当然包括其他的数组),生成一个新的包含传递数据的NumPy数组。

In [10]:
data1 = [6,7.5, 8, 0,1]
arr1 = np.array(data1)
arr
Out[10]:
array([6. , 7.5, 8. , 0. , 1. ])
In [11]:
# 嵌套序列,例如同等长度的列表,将会自动转换成多维数组
data2 = [[1,2,3,4], [5,6,7,8]]
arr2 = np.array(data2)
arr
Out[11]:
array([[1, 2, 3, 4],
       [5, 6, 7, 8]])
In [13]:
arr2.ndim
Out[13]:
2
In [14]:
arr2.sh
Out[14]:
(2, 4)
In [15]:
np.zeros(10)
Out[15]:
array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])
In [17]:
np.zeros((3,6))
Out[17]:
array([[0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0.]])
In [16]:
np.empty((2,3,2))
Out[16]:
array([[[6.90552565e-310, 6.90552566e-310],
        [4.65229676e-310, 6.90552608e-310],
        [6.90552594e-310, 6.90552568e-310]],

       [[6.90552583e-310, 6.90552566e-310],
        [6.90552565e-310, 4.65229676e-310],
        [6.90552608e-310, 6.90552594e-310]]])
In [18]:
# arange是Python内建函数range的数组版
np.arange(15
Out[18]:
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14])
  • np.ones

  • np.ones_like 根据所给的数组生成一个形状一样的全1数组

  • np.full 根据给定的形状和数据类型生成制定数值的数组

  • np.full_like 根据所给的数组生成一个形状一样但内容是指定数值的数组

  • eye, identity 生成一个NxN特征矩阵(对角线都是1,其余位置是0)

NumPy 数据类型

  • np.int8, np.uint8i1, u1

  • np.int16, np.uint16i2, u2

  • np.int32, np.uint32i4, u4

  • np.int64, np.uint64i8, u8

  • np.float16f2

  • np.float32: f4f

  • np.float64: f8d

  • np.float128: f16g

  • 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类型

In [19]:
# 使用astype方法显式的转换数组的数据类型
arr = np.array([1,2,3,4,5])
arr.dtype
Out[19]:
dtype('int64')
In [20]:
float_arr = arr.astype(np.float64)
float_arr.dty
Out[20]:
dtype('float64')
In [21]:
arr = np.array([3.7, -1.2, -2.6, 0.5, 12.9, 10.1])
arr
Out[21]:
array([ 3.7, -1.2, -2.6,  0.5, 12.9, 10.1])
In [22]:
arr.astype(np.int32)
Out[22]:
array([ 3, -1, -2,  0, 12, 10], dtype=int32)
In [24]:
# 如果有一个数组,里面的元素都是表达数字含义的字符串,也可以通过astype将字符串转换为数字
numeric_strings = np.array(['1.25', '-9.6', '42'], dtype=np.string_)
numeric_strings.astype(float)
Out[24]:
array([ 1.25, -9.6 , 42.  ])

在Numpy中,当使用np.string_类型做字符串数据要小心,因为NumPy会修改它的大小或删除输入且不发出告警。pandas在处理数据时有更直观的开箱型操作。

NumPy数组算术

数组之所以重要是因为它允许你进行批量操作而无须任何for循环。这种特性称为向量化。任何在两个等尺寸数组之间的算术操作都应用了逐元素操作的方式:

In [25]:
arr = np.array([[1.,2.,3.],[4.,5.,6.]])
Out[25]:
array([[1., 2., 3.],
       [4., 5., 6.]])
In [26]:
arr * arr
Out[26]:
array([[ 1.,  4.,  9.],
       [16., 25., 36.]])
In [29]:
np.dot(arr,arr.)
Out[29]:
array([[14., 32.],
       [32., 77.]])
In [33]:
arr2 = np.array([[0.,4.,1.],[7.,2.,12.]])
arr
Out[33]:
array([[ 0.,  4.,  1.],
       [ 7.,  2., 12.]])
In [34]:
arr2 > ar
Out[34]:
array([[False,  True, False],
       [ True, False,  True]])

基础索引与切片

In [36]:
arr = np.arange(10)
arr
Out[36]:
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
In [37]:
arr[5]
Out[37]:
5
In [38]:
arr[5:8]
Out[38]:
array([5, 6, 7])
In [40]:
arr[5:8]=12
arr
Out[40]:
array([ 0,  1,  2,  3,  4, 12, 12, 12,  8,  9])

如上,如果你传入了一个数值给数组的切片,例如arr[5:8]=12,数值被传递给了整个切片。区别于Python的内建列表,数组的切片是原数组的视图。这意味着并不是被复制了,任何对于视图的修改都会反映到原数组上。

In [41]:
arr_slice = arr[5:8]
arr_slice
Out[41]:
array([12, 12, 12])
In [42]:
arr_slice[1] = 12345
Out[42]:
array([    0,     1,     2,     3,     4,    12, 12345,    12,     8,
           9])
In [43]:
# 不写切片值的[:]将会引用数组的所有值
arr_slice[:] = 64
ar
Out[43]:
array([ 0,  1,  2,  3,  4, 64, 64, 64,  8,  9])

如果你想要一份数组切片的拷贝而不是一份视图的话,你必须显式的复制这个数组,例如arr[5:8].copy()

In [44]:
arr2d = np.array([[1,2,3],[4,5,6],[7,8,9]])
arr2d[2]
Out[44]:
array([7, 8, 9])
In [45]:
arr2d[0][2]
Out[45]:
3
In [46]:
arr2d[0,2]
Out[46]:
3
In [47]:
arr3d = np.array([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]])
ar
Out[47]:
array([[[ 1,  2,  3],
        [ 4,  5,  6]],

       [[ 7,  8,  9],
        [10, 11, 12]]])
In [48]:
arr3d[0]
Out[48]:
array([[1, 2, 3],
       [4, 5, 6]])
In [49]:
old_values = arr3d[0].copy()
arr3d[0] = 42
arr3d
Out[49]:
array([[[42, 42, 42],
        [42, 42, 42]],

       [[ 7,  8,  9],
        [10, 11, 12]]])
In [50]:
arr3d[0] = old_values
arr3d
Out[50]:
array([[[ 1,  2,  3],
        [ 4,  5,  6]],

       [[ 7,  8,  9],
        [10, 11, 12]]])
In [51]:
arr2d
Out[51]:
array([[1, 2, 3],
       [4, 5, 6],
       [7, 8, 9]])
In [52]:
arr2d[:2, 1:]
Out[52]:
array([[2, 3],
       [5, 6]])

布尔索引

In [53]:
names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])
data = np.random.randn(7,4)
names
Out[53]:
array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'], dtype='<U4')
In [54]:
data
Out[54]:
array([[-1.40723779, -0.95350849, -0.88035233,  0.60492995],
       [ 0.96081441,  0.70276627, -0.00314785, -2.21065305],
       [-0.26599127,  1.05289776, -0.67616289,  1.91445053],
       [-0.09775983, -0.9024056 ,  0.00630078, -0.39676396],
       [ 0.87178528,  0.8119386 ,  0.11641328,  0.31187568],
       [-0.14625233, -1.33247212,  0.39952828,  0.36762071],
       [ 1.2372177 , -0.49359266,  1.62619821,  0.3804326 ]])
In [55]:
names = 'Bob'
Out[55]:
array([ True, False, False,  True, False, False, False])
In [56]:
data[names == 'Bob'] # 等同于data[~(names != 'Bob')]
Out[56]:
array([[-1.40723779, -0.95350849, -0.88035233,  0.60492995],
       [-0.09775983, -0.9024056 ,  0.00630078, -0.39676396]])
In [57]:
data[names == 'Bob', 2:]
Out[57]:
array([[-0.88035233,  0.60492995],
       [ 0.00630078, -0.39676396]])
In [58]:
data[names == 'Bob', 3]
Out[58]:
array([ 0.60492995, -0.39676396])
In [60]:
names != 'Bob'
Out[60]:
array([False,  True,  True, False,  True,  True,  True])
In [62]:
data[~(names == 'Bob')]
Out[62]:
array([[ 0.96081441,  0.70276627, -0.00314785, -2.21065305],
       [-0.26599127,  1.05289776, -0.67616289,  1.91445053],
       [ 0.87178528,  0.8119386 ,  0.11641328,  0.31187568],
       [-0.14625233, -1.33247212,  0.39952828,  0.36762071],
       [ 1.2372177 , -0.49359266,  1.62619821,  0.3804326 ]])
In [63]:
# 当要选择三个名字中的两个时,可以对多个布尔值条件进行联合,需要使用数学操作符&和|
mask = (names == 'Bob') | (names == 'Will')
Out[63]:
array([ True, False,  True,  True,  True, False, False])
In [65]:
data[mask]
Out[65]:
array([[-1.40723779, -0.95350849, -0.88035233,  0.60492995],
       [-0.26599127,  1.05289776, -0.67616289,  1.91445053],
       [-0.09775983, -0.9024056 ,  0.00630078, -0.39676396],
       [ 0.87178528,  0.8119386 ,  0.11641328,  0.31187568]])

使用布尔值索引选择数据i时,总是生成数据的拷贝,即使返回的数组并没有任何变化。Python的关键字andor对布尔值数组并没有用,请使用&|来代替

In [66]:
# 将data中所有的负值设为0
data[data<0] =0 
data
Out[66]:
array([[0.        , 0.        , 0.        , 0.60492995],
       [0.96081441, 0.70276627, 0.        , 0.        ],
       [0.        , 1.05289776, 0.        , 1.91445053],
       [0.        , 0.        , 0.00630078, 0.        ],
       [0.87178528, 0.8119386 , 0.11641328, 0.31187568],
       [0.        , 0.        , 0.39952828, 0.36762071],
       [1.2372177 , 0.        , 1.62619821, 0.3804326 ]])

神奇索引

神奇索引是NumPy中的术语,用于描述使用整数数组进行数据索引。

In [67]:
arr = np.empty((8,4))
for i in range(8):
    arr[i] = i 
Out[67]:
array([[0., 0., 0., 0.],
       [1., 1., 1., 1.],
       [2., 2., 2., 2.],
       [3., 3., 3., 3.],
       [4., 4., 4., 4.],
       [5., 5., 5., 5.],
       [6., 6., 6., 6.],
       [7., 7., 7., 7.]])
In [69]:
# 为了选出一个符合特定顺序的子集,可以简单的通过传递一个包含指明所需顺序的列表或数组来完成
arr[[4,3,0,6]]
Out[69]:
array([[4., 4., 4., 4.],
       [3., 3., 3., 3.],
       [0., 0., 0., 0.],
       [6., 6., 6., 6.]])
In [70]:
# 如果使用负的索引,将从尾部进行选择
arr[[-3, -5, -7]]
Out[70]:
array([[5., 5., 5., 5.],
       [3., 3., 3., 3.],
       [1., 1., 1., 1.]])
In [71]:
# 传递多个索引数组时情况有些不同,这样会根据每个索引元组对应的元素选出一个一维数组
arr = np.arange(32).reshape((8,4))
arr
Out[71]:
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],
       [24, 25, 26, 27],
       [28, 29, 30, 31]])
In [72]:
arr[[1,5,7,2], [0,3,1,2]]
Out[72]:
array([ 4, 23, 29, 10])
In [73]:
arr[[1,5,7,2]][:,[0,3,1,2]]
Out[73]:
array([[ 4,  7,  5,  6],
       [20, 23, 21, 22],
       [28, 31, 29, 30],
       [ 8, 11,  9, 10]])

数组转置和换轴

In [2]:
import numpy as n
arr = np.arange(15).reshape((3,5))
In [3]:
arr
Out[3]:
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14]])
In [4]:
arr.T
Out[4]:
array([[ 0,  5, 10],
       [ 1,  6, 11],
       [ 2,  7, 12],
       [ 3,  8, 13],
       [ 4,  9, 14]])
In [5]:
arr = np.random.randn(6,3)
arr
Out[5]:
array([[-0.53468757,  0.37701975, -0.49617229],
       [-0.5155967 ,  1.35449345, -0.67493271],
       [ 0.35225839, -0.65900322,  0.63551103],
       [-0.41058249, -0.23174063,  1.20308421],
       [ 0.00574297, -0.79370055, -0.42987623],
       [-1.20742163,  0.76717641, -0.94967373]])
In [6]:
np.dot(arr.T, arr)
Out[6]:
array([[ 2.30229468, -1.96781449,  1.48737685],
       [-1.96781449,  3.68330559, -2.18624028],
       [ 1.48737685, -2.18624028,  3.63968075]])

对于更高维度的数组,transpose方法可以接受包含轴编号的元组,用于置换轴:

In [7]:
arr = np.arange(16).reshape((2,2,4))
arr
Out[7]:
array([[[ 0,  1,  2,  3],
        [ 4,  5,  6,  7]],

       [[ 8,  9, 10, 11],
        [12, 13, 14, 15]]])

下面,轴已经被重新排序,使得原先的第二个轴变为第一个,原先的第一个变成第二个,最后一个轴并没有改变。

In [8]:
arr.transpose((1,0,2))
Out[8]:
array([[[ 0,  1,  2,  3],
        [ 8,  9, 10, 11]],

       [[ 4,  5,  6,  7],
        [12, 13, 14, 15]]])

使用.T进行转置是换轴的一个特殊案例。ndarray有一个swapaxes方法,该方法接收一对轴编号作为参数,并对轴进行调整用于重组数据

In [9]:
arr
Out[9]:
array([[[ 0,  1,  2,  3],
        [ 4,  5,  6,  7]],

       [[ 8,  9, 10, 11],
        [12, 13, 14, 15]]])
In [10]:
arr.swapaxes(1,2) # swapaxes返回的是数据的视图,而没有对数据进行复制
Out[10]:
array([[[ 0,  4],
        [ 1,  5],
        [ 2,  6],
        [ 3,  7]],

       [[ 8, 12],
        [ 9, 13],
        [10, 14],
        [11, 15]]])

通用函数:快速的逐元素数组函数

通用函数,也称为ufunc,是一种在ndarray数据中进行逐元素操作的函数。某些简单函数接收一个或多个标量数值,并产生一个或多个标量结果,而通用函数就是对这些简单函数的向量化封装

In [11]:
arr = np.arange(10)
arr
Out[11]:
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
In [12]:
np.sqrt(arr)
Out[12]:
array([0.        , 1.        , 1.41421356, 1.73205081, 2.        ,
       2.23606798, 2.44948974, 2.64575131, 2.82842712, 3.        ])
In [18]:
np.square(arr)
Out[18]:
array([37.12716835,  0.13897775, 27.64775873, 24.25377751, 48.54748728,
        8.84302686,  8.80928513])
In [13]:
np.exp(arr)
Out[13]:
array([1.00000000e+00, 2.71828183e+00, 7.38905610e+00, 2.00855369e+01,
       5.45981500e+01, 1.48413159e+02, 4.03428793e+02, 1.09663316e+03,
       2.98095799e+03, 8.10308393e+03])
In [14]:
x = np.random.randn(8)
y = np.random.randn(8)
np.maximum(x, y) # 逐个元素对x和y中元素的最大值计算出来。
Out[14]:
array([ 0.12917854, -0.12455002, -1.04568088,  1.58457963,  0.29806639,
        1.02968317,  0.6528741 , -0.03781262])
In [15]:
# modf是Python内建函数divmod的向量化版本,它返回一个浮点值数组中的小数部分和整数部分
arr = np.random.randn(7) * 5
arr
Out[15]:
array([ 6.09320674, -0.3727972 ,  5.25811361, -4.92481243,  6.96760269,
        2.97372273,  2.96804399])
In [16]:
remainder, whole_part = np.modf(arr)
remainde
Out[16]:
array([ 0.09320674, -0.3727972 ,  0.25811361, -0.92481243,  0.96760269,
        0.97372273,  0.96804399])
In [17]:
whole_part 
Out[17]:
array([ 6., -0.,  5., -4.,  6.,  2.,  2.])
In [19]:
np.abs(whole_part)
Out[19]:
array([6., 0., 5., 4., 6., 2., 2.])
In [20]:
arr
Out[20]:
array([ 6.09320674, -0.3727972 ,  5.25811361, -4.92481243,  6.96760269,
        2.97372273,  2.96804399])
In [21]:
np.exp(arr)
Out[21]:
array([4.42839206e+02, 6.88804911e-01, 1.92118738e+02, 7.26408865e-03,
       1.06167454e+03, 1.95646179e+01, 1.94538305e+01])
In [22]:
np.log(arr) # np.log10, np.log2, np.log1p
/Users/swami/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:1: RuntimeWarning: invalid value encountered in log
  """Entry point for launching an IPython kernel.
Out[22]:
array([1.8071745 ,        nan, 1.65977233,        nan, 1.94127122,
       1.08981461, 1.08790315])
In [23]:
np.sign(arr) # 计算每个元素的符号值,1(正数), 0(0), -1(负数)
Out[23]:
array([ 1., -1.,  1., -1.,  1.,  1.,  1.])
In [24]:
np.ceil(arr)
Out[24]:
array([ 7., -0.,  6., -4.,  7.,  3.,  3.])
In [25]:
np.floor(arr)
Out[25]:
array([ 6., -1.,  5., -5.,  6.,  2.,  2.])
In [26]:
np.rint(arr) # 将元素保留到整数位,并保持dtype
Out[26]:
array([ 6., -0.,  5., -5.,  7.,  3.,  3.])
In [27]:
np.isnan(arr) 
Out[27]:
array([False, False, False, False, False, False, False])
In [28]:
np.isfinite(arr) # 返回数组中的元素是否有限(非inf,非NaN),是否无限的,形式为布尔值数组
Out[28]:
array([ True,  True,  True,  True,  True,  True,  True])
In [29]:
np.isinf(arr)
Out[29]:
array([False, False, False, False, False, False, False])
In [30]:
np.logical_not(np.isinf(arr)) # 对数组的元素按位取反(与~arr效果一致)
Out[30]:
array([ True,  True,  True,  True,  True,  True,  True])

其它的一元通用函数还有: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)对生成一个二维矩阵:

In [31]:
points = np.arange(-5, 5, 0.01) # 1000 equally spaced points
xs, ys = np.meshgrid(points, points)
ys
Out[31]:
array([[-5.  , -5.  , -5.  , ..., -5.  , -5.  , -5.  ],
       [-4.99, -4.99, -4.99, ..., -4.99, -4.99, -4.99],
       [-4.98, -4.98, -4.98, ..., -4.98, -4.98, -4.98],
       ...,
       [ 4.97,  4.97,  4.97, ...,  4.97,  4.97,  4.97],
       [ 4.98,  4.98,  4.98, ...,  4.98,  4.98,  4.98],
       [ 4.99,  4.99,  4.99, ...,  4.99,  4.99,  4.99]])
In [32]:
xs
Out[32]:
array([[-5.  , -4.99, -4.98, ...,  4.97,  4.98,  4.99],
       [-5.  , -4.99, -4.98, ...,  4.97,  4.98,  4.99],
       [-5.  , -4.99, -4.98, ...,  4.97,  4.98,  4.99],
       ...,
       [-5.  , -4.99, -4.98, ...,  4.97,  4.98,  4.99],
       [-5.  , -4.99, -4.98, ...,  4.97,  4.98,  4.99],
       [-5.  , -4.99, -4.98, ...,  4.97,  4.98,  4.99]])
In [34]:
z = np.sqrt(xs**2 + ys**2)
z
Out[34]:
array([[7.07106781, 7.06400028, 7.05693985, ..., 7.04988652, 7.05693985,
        7.06400028],
       [7.06400028, 7.05692568, 7.04985815, ..., 7.04279774, 7.04985815,
        7.05692568],
       [7.05693985, 7.04985815, 7.04278354, ..., 7.03571603, 7.04278354,
        7.04985815],
       ...,
       [7.04988652, 7.04279774, 7.03571603, ..., 7.0286414 , 7.03571603,
        7.04279774],
       [7.05693985, 7.04985815, 7.04278354, ..., 7.03571603, 7.04278354,
        7.04985815],
       [7.06400028, 7.05692568, 7.04985815, ..., 7.04279774, 7.04985815,
        7.05692568]])
In [38]:
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")
Out[38]:
Text(0.5,1,'Image plot of $\\sqrt{x^2 + y^2}$ for a grid of val uses')

将条件逻辑作为数组操作

np.where函数是三元表达式x if condition else y的向量化版本。

In [39]:
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中对应元素

In [40]:
result = [(x if c else y) for x, y, c in zip(xarr, yarr, cond)]
reuslt
Out[40]:
[1.1, 2.2, 1.3, 1.4, 2.5]

这样会产生多个问题,首先,如果数组很大时,速度会很慢,因为所有工作是通过Python解释器完成,其次当数组是多维时,就无法奏效,而使用np.where可以非常简单的完成:

In [41]:
result = np.where(cond, xarr, yarr)
resu
Out[41]:
array([1.1, 2.2, 1.3, 1.4, 2.5])
In [42]:
arr = np.random.randn(4,4)
a
Out[42]:
array([[ 1.19563643, -0.48569343, -0.82071904,  1.74463621],
       [ 1.01494104,  1.35546483, -1.56814888,  0.5125132 ],
       [ 0.89583173,  0.08753098,  2.03485622, -0.15298996],
       [ 0.5956699 , -0.88800159, -1.33856888,  0.71445159]])
In [43]:
arr > 0
Out[43]:
array([[ True, False, False,  True],
       [ True,  True, False,  True],
       [ True,  True,  True, False],
       [ True, False, False,  True]])
In [44]:
np.where(arr>0, 2, arr)
Out[44]:
array([[ 2.        , -0.48569343, -0.82071904,  2.        ],
       [ 2.        ,  2.        , -1.56814888,  2.        ],
       [ 2.        ,  2.        ,  2.        , -0.15298996],
       [ 2.        , -0.88800159, -1.33856888,  2.        ]])

数学和统计方法

In [45]:
arr = np.random.randn(5,4)
arr
Out[45]:
array([[ 2.34841514,  2.21796368,  0.4433716 ,  0.06734774],
       [ 1.62446024,  0.56166645, -0.10201846,  1.09125699],
       [ 0.31673012, -0.232818  ,  0.56427394,  0.21180094],
       [ 0.13108302,  0.64549397,  0.74952159,  0.06585079],
       [ 0.92547438,  0.18525912, -0.63863499,  1.94992433]])
In [46]:
arr.mean()
Out[46]:
0.6563211283787082
In [47]:
np.mean(arr)
Out[47]:
0.6563211283787082
In [48]:
arr.mean(axis=1) # 计算每一列的平均值
Out[48]:
array([1.26927454, 0.79384131, 0.21499675, 0.39798734, 0.60550571])
In [50]:
arr.sum(axis=0) # 计算每一行的累和
Out[50]:
array([5.34616289, 3.37756522, 1.01651367, 3.38618079])
In [51]:
arr.cumsum()
Out[51]:
array([ 2.34841514,  4.56637882,  5.00975041,  5.07709816,  6.7015584 ,
        7.26322485,  7.16120639,  8.25246338,  8.5691935 ,  8.3363755 ,
        8.90064944,  9.11245038,  9.2435334 ,  9.88902736, 10.63854895,
       10.70439974, 11.62987412, 11.81513323, 11.17649824, 13.12642257])
In [52]:
arr = np.array([[0,1,2], [3,4,5], [6,7,8]])
arr
Out[52]:
array([[0, 1, 2],
       [3, 4, 5],
       [6, 7, 8]])
In [53]:
arr.cumsum(axis=0)
Out[53]:
array([[ 0,  1,  2],
       [ 3,  5,  7],
       [ 9, 12, 15]])
In [54]:
arr.cumprod(axis=1)
Out[54]:
array([[  0,   0,   0],
       [  3,  12,  60],
       [  6,  42, 336]])

布尔值数组的方法

In [55]:
arr = np.random.randn(100)
(arr > 0 ).sum()
Out[55]:
62
In [58]:
bools = np.array([False, False, True, False])
bools.any()
Out[58]:
True
In [59]:
bools.all()
Out[59]:
False

排序

In [60]:
arr = np.random.randn(6)
arr
Out[60]:
array([-0.84767388, -0.08819467,  1.79066298, -0.09747501, -1.66094627,
        0.00754253])
In [61]:
arr.sort()
In [62]:
arr
Out[62]:
array([-1.66094627, -0.84767388, -0.09747501, -0.08819467,  0.00754253,
        1.79066298])
In [63]:
arr = np.random.randm
Out[63]:
array([[ 1.72894411, -0.31446392, -0.78671809],
       [ 1.32883796,  1.25300127,  0.56379919],
       [ 0.66955133,  0.90371238, -1.43726029],
       [ 0.67210065,  0.71654254,  0.21886142],
       [-0.09631025, -0.65655594, -0.49350202]])
In [65]:
arr.sort(1)
arr
Out[65]:
array([[-0.78671809, -0.31446392,  1.72894411],
       [ 0.56379919,  1.25300127,  1.32883796],
       [-1.43726029,  0.66955133,  0.90371238],
       [ 0.21886142,  0.67210065,  0.71654254],
       [-0.65655594, -0.49350202, -0.09631025]])
In [66]:
names = np.array(['Bob', 'Joe', 'Eill', 'Bob', 'Eill', 'Joe', 'Joe'])
np.unique(names)
Out[66]:
array(['Bob', 'Eill', 'Joe'], dtype='<U4')
In [67]:
sorted(set(names))
Out[67]:
['Bob', 'Eill', 'Joe']

np.in1d检查一个数组中的值是否在另外一个数组中,并返回一个布尔值数组

In [70]:
values = np.array([6,0,0,3,2,5,6])
np.in1d(values, [2,3,6])
Out[70]:
array([ True, False, False,  True,  True, False,  True])

类似方法还有intersect1d(x,y), union1d(x,y), in1d(x,y), setdiff1d(x, y), setxor1d(x, y)

使用数组进行文件输入和输出

NumPy可以在硬盘中将数据以文本或二进制文件的形式进行存入硬盘或由硬盘载入。np.savenp.load是高效存取硬盘数据的两大工具函数。数组在默认情况下是以未压缩的格式进行存储的,后缀名是.npy:

In [2]:
import
arr = np.arange(10)
np.save('test_array', arr)
In [4]:
np.load('test_array.npy')
Out[4]:
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
In [5]:
# 将数组作为参数传递给该函数,用于在未压缩文件中保存多个数组
np.savez('test_archive.npz', a = arr, b = arr*2)
In [6]:
arch = np.load('test_archive')  
In [8]:
arch
Out[8]:
<numpy.lib.npyio.NpzFile at 0x11b3a8710>
In [9]:
## 伪随机数生成

samples = np.random.normal(size = (4,4))
samples
Out[9]:
array([[ 1.25583042,  0.6243777 , -1.65699234,  0.01303413],
       [ 0.39746531, -0.5245762 ,  0.37817187, -0.0928846 ],
       [ 0.77379434, -1.88568196,  0.33259457,  0.95950173],
       [ 1.51585553, -0.0541324 ,  0.09722468, -0.87536079]])
In [10]:
rng = np.random.RandomState(1234)
rng.randn(10)
Out[10]:
array([ 0.47143516, -1.19097569,  1.43270697, -0.3126519 , -0.72058873,
        0.88716294,  0.85958841, -0.6365235 ,  0.01569637, -2.24268495])
In [ ]: