因为网上对这个函数的很多教程,解释不够通俗易懂,或者说规律不够简单明白,所以我总结了一下,写成文分享给大家。
函数的语法是np.tile(a, reps),a表示类数组元素(不仅可以是ndarray数组,也可以是列表、元组等),reps用来定义各个方向上的拷贝数量。reps参数可以记忆成repeat shape,也即拷贝性扩展的形状。
假设a的原形状为(2, 3),reps=(2)或reps=2(这两种表示方式等价)时,返回数组的形状计算规律如图中所示“右对齐+逐个元素相乘+缺则补一”。可得返回数组的形状为(2, 6)。
import numpy as np
arr = np.array([[1, 2, 3], [4, 5, 6]])
arr1 = np.tile(arr,2)
print(arr1) # [[1 2 3 1 2 3],[4 5 6 4 5 6]]
print(arr1.shape) # (2, 6)
从上面的形状变化规律我们大概也可以猜出,到底是怎么拷贝的?以下面的代码为例子进行更深度的阐释。
import numpy as np
arr = np.array([[1, 2, 3], [4, 5, 6]])
arr1 = np.tile(arr, (1, 2, 1, 3))
print(arr1)
print('-------------------')
print(arr1.shape)
# [[[[1 2 3 1 2 3 1 2 3]
# [4 5 6 4 5 6 4 5 6]]
# [[1 2 3 1 2 3 1 2 3]
# [4 5 6 4 5 6 4 5 6]]]]
# -------------------
# (1, 2, 2, 9)
原数组的形状为(2, 3),而repr=(1, 2, 1, 3),从本文的第一部分得到的粗体字对应的规律,可以得到arr.shape的第2个参数3对应repr的第4个参数3,arr.shape的第1个参数2对应repr的第3个参数1。以此类推,可以得到返回数组的形状为(1, 2, 2, 9)。
假设repr的参数是从右向左开始起作用,那么第4个参数3的作用就是使arr在axis=1的维度上拷贝3份得到[[1, 2, 3, 1, 2, 3, 1, 2, 3], [4, 5, 6, 4, 5, 6, 4, 5, 6]]。
同理也可推得第3个参数1的作用就是使arr在axis=0的维度上拷贝1份(也即不变)得到[[1, 2, 3, 1, 2, 3, 1, 2, 3], [4, 5, 6, 4, 5, 6, 4, 5, 6]]。
接着进行!剩下的两个参数就有点难度了。
repr的第2个参数2的作用是什么呢?就是将第2步得到的数组拷贝两份,得到[[1, 2, 3, 1, 2, 3, 1, 2, 3], [4, 5, 6, 4, 5, 6, 4, 5, 6]],[[1, 2, 3, 1, 2, 3, 1, 2, 3], [4, 5, 6, 4, 5, 6, 4, 5, 6]];又因为这时候已经不是在原数组的形状维度上了,所以必须升维增加中括号,得到[[[1, 2, 3, 1, 2, 3, 1, 2, 3], [4, 5, 6, 4, 5, 6, 4, 5, 6]],[[1, 2, 3, 1, 2, 3, 1, 2, 3], [4, 5, 6, 4, 5, 6, 4, 5, 6]]]。
同第3步的道理,也可以推得第1个参数1的作用是将第3步得到的数组拷贝一份(结果不同于第3步的数组,因为不是一个维度),那么得到[[[1, 2, 3, 1, 2, 3, 1, 2, 3], [4, 5, 6, 4, 5, 6, 4, 5, 6]],[[1, 2, 3, 1, 2, 3, 1, 2, 3], [4, 5, 6, 4, 5, 6, 4, 5, 6]]],又因为第1个参数对应的维度是比第3步得到的数组所拥有的维度还要更高一维的,所以又要加1个中括号,得到[[[[1, 2, 3, 1, 2, 3, 1, 2, 3], [4, 5, 6, 4, 5, 6, 4, 5, 6]],[[1, 2, 3, 1, 2, 3, 1, 2, 3], [4, 5, 6, 4, 5, 6, 4, 5, 6]]]]。
检查一下程序运行结果,和我们用这个流程手算的相同,因此可以应用。
下面是对列表用*操作符进行扩展,可见不管是一维列表还是多维列表,都是在axis=0的方向上进行拷贝性扩展。注意不能乘以一个浮点数,因为列表类型不支持numpy中的broadcast机制,所以没办法让一个标量和一个矢量进行点乘。
print([1,2,3] * 2)
print([[1,2],[4,5]] * 2)
# [1, 2, 3, 1, 2, 3]
# [[1, 2], [4, 5], [1, 2], [4, 5]]
下面是对字符串用*操作符进行扩展。
print('I love you, my honey.' * 2)
print('老婆,我错了' * 3)
# I love you, my honey.I love you, my honey.
# 老婆,我错了老婆,我错了老婆,我错了
对于数组,如果直接乘以某一个整数,会生成一个对每一个元素都乘以这个整数的数组,因为它有broadcast机制,允许这样相乘,从而也导致无法直接通过*操作符来实现对数组的拷贝性扩展。
方法总比困难多,直接不行绕条道玩“间接”,也是可以的,通过list()函数转换成列表后再用不就行了!
numpy.repeat(arr_like, repeats, axis)和ndarray.repeat(repeats, axis)用法相近;它们都无法改变原arr,都有经扩展后的返回值;唯一的不同是后者只能对ndarray使用,而前者对ndarray以外的列表、元组、字符串等也能使用。
# 1、单个数值拷贝扩展变成了一个数组
print(np.repeat(3, 4)) # array([3, 3, 3, 3])
# 2、如果axis=None,那么传入的a展成一维后再逐元素拷贝扩展,最后生成一维数组
x = np.array([[1,2],[3,4]])
print(np.repeat(x, 2)) # array([1, 1, 2, 2, 3, 3, 4, 4])
# 3、如果axis=1,则返回的数组维度不会改变
print(np.repeat(x, 3, axis=1))
#array([[1, 1, 1, 2, 2, 2],
# [3, 3, 3, 4, 4, 4]])
# 4、如果repeats不等于一个整数,而是一个列表,列表中的每一个元素对应着axis指定方向上的元素,因此这种语法适用于对每一个单元拷贝份数不同的情况
print(np.repeat(x, [1, 2], axis=0))
#array([[1, 2],
# [3, 4],
# [3, 4]])
# 5、当repeats是一个列表时,如果len(repeats) != a.shape[axis],就会报错
print(np.repeat(x, [1, 2, 3], axis=0)) # ValueError: operands could not be broadcast together with shape (2,) (3,)
3.3.1共同点:
都可以实现拷贝性扩展。
3.3.2差别点
(1)numpy.tile()可以实现升维,但是numpy.repeat()只能在现有维度框架上拷贝扩展。
(2)numpy.tile()是把指定维度上的数据整体进行拷贝,也即如果原来是[1,2,3],拷贝后应该是[1,2,3,1,2,3],而numpy.repeat()是逐个进行拷贝,如果原来是[1,2,3],拷贝后应该是[1,1,2,2,3,3]。