Numpy的终极备忘录
作者|Rashida Nasrin Sucky
编译|VK
来源|Towards Data Science
Python是开源的。对于使用python的数据科学家来说,Numpy这个库是必不可少的。其他一些基本的库,如Pandas,Scipy是建立在Numpy的基础上。所以我决定做一份备忘录。这里我包括了到目前为止使用的所有Numpy函数。我相信这些函数将足以让你在日常工作中作为数据科学家或数据分析员完成你的工作。
我将从非常基本的Numpy功能开始,慢慢地向更高级的功能移动。但是使用Numpy很容易。在这里你不会发现任何复杂的编码技巧。
什么是Numpy
在Numpy文档中,Numpy的定义如下:
NumPy是Python中科学计算的基本包。它是一个Python库,它提供多维数组对象、各种派生对象(如掩码数组和矩阵)以及数组上快速操作的各种例程,包括数学、逻辑、形状处理、排序、选择、I/O、离散傅立叶变换、基本线性代数,基本的统计操作,随机模拟等等。
我每天都用这个库。如果是python用户,大多数数据科学家都会这么做。它快速,易于使用,理解,简单。我不想写太多关于它是如何和为什么这么好的。因为在阅读本文的过程中,你将亲眼看到这一点。
我的目标是记录Numpy每天使用的方法。
正如文章名所说,这是一本关于Numpy的指南。它也可以用作备忘录。如果你使用Numpy库或计划将来使用,或正在学习,此页面可以成为你日常生活的一个很好的资源。
这里将讨论以下主题:
-
Numpy数组基础知识
-
重复
-
数学运算
-
统计
-
初始化不同类型的数组
-
重新排列或重新组织数组
-
数组的索引与切片
-
添加行或列
-
追加、插入、删除和排序
-
随机
-
文件导入、保存和加载
我们开始吧!!
Numpy数组基础知识
整个练习我都用了一个Jupyter Notebook。第一个导入Numpy。
import numpy as np
做一个Numpy数组。为此,我们需要传递一个Python列表。
输入:
a = np.array([1,2,3])
a
输出:
array([1, 2, 3])
在数组'a'中,我使用了所有的整数。现在,制作一个浮点数组:
输入:
b = np.array([[9.0, 10.0, 6.0], [6.0,1.0,7.0]])
b
输出:
array([[ 9., 10., 6.],
[ 6., 1., 7.]])
让我们试着用浮点型和浮点型数组:
输入:
np.array([1, 3.0, 0.004, -2])
输出:
array([ 1. , 3. , 0.004, -2. ])
注意,Numpy自动将整数转换为浮点!
找出数组a和b的尺寸:
输入:
a.ndim
输出:
1
输入:
b.ndim
输出:
2
数组“a”是一维数组,数组b是二维数组。
现在,找出数组“a”和“b”的形状:
输入:
a.shape
输出:
(3,)
输入:
b.shape
输出:
(2, 3)
数组“a”是一维数组。它只有一个值。但是数组b是一个二维数组。所以,它的形状是2×3。这意味着它有2行3列。
查找数组的长度:
输入:
len(a)
输出:
3
输入:
len(b)
输出:
2
数组a的长度是3,因为它里面有3个元素。数组“b”是一个二维数组。因此,数组的长度并不意味着其中元素的数量。长度表示其中一维数组的数量或其中的行数。它有两行。长度是2。
重复
有几种不同的方法可以重复数组的元素。如果你想重复整个数组,
输入:
np.array([2,4,6]*4)
输出:
array([2, 4, 6, 2, 4, 6, 2, 4, 6, 2, 4, 6])
看,数组[2,4,6]被重复了4次。
下面是如何做元素级的重复,
输入:
np.repeat([1,2,3], 3)
输出:
array([1, 1, 1, 2, 2, 2, 3, 3, 3])
这次每个元素重复3次。
我们把这个用于二维数组,
输入:
arr = np.array([[2, 4, 6]])
arr
输出:
array([[2, 4, 6]])
现在,在它上面重复:
输入:
np.repeat(arr,3,axis=0)
输出:
array([[2, 4, 6],
[2, 4, 6],
[2, 4, 6]])
这里,我们提到axis=0。所以,重复发生在0轴方向或行方向。
输入:
np.repeat(arr,3,axis=1)
输出:
array([[2, 2, 2, 4, 4, 4, 6, 6, 6]])
轴1指示列的方向。所以,重复发生在列的方向上。
数学运算
在这一节中,我将展示数学运算。大多数操作都是不言而喻的。我将从一个数组的数学运算开始。
输入:
a = np.array([1,2,3,4])
a
输出:
array([1, 2, 3, 4])
输入:
a+2
输出:
array([3, 4, 5, 6])
它向数组的每个元素添加2。
输入:
a-2
输出:
array([-1, 0, 1, 2])
你可以简单地使用类似的操作,例如:
输入:
a/2
输出:
array([0.5, 1. , 1.5, 2. ])
输入:
a**2
输出:
array([ 1, 4, 9, 16], dtype=int32)
两个星号表示指数。“a”中的每个元素都是平方的。
输入:
np.sqrt(a) # 平方根
输出:
array([1. , 1.41421356, 1.73205081, 2. ])
我们还可以执行一些三角运算:
输入:
np.cos(a)
输出:
array([ 0.54030231, -0.41614684, -0.9899925 , -0.65364362])
输入:
np.sin(a)
输出:
array([ 0.84147098, 0.90929743, 0.14112001, -0.7568025 ])
输入:
np.tan(a)
输出:
array([ 1.55740772, -2.18503986, -0.14254654, 1.15782128])
现在看看我们如何在两个数组或矩阵中做一些数学运算。首先,再做一个数组,
输入:
b = np.array([3,4,5,6])
输出:
array([3, 4, 5, 6])
作为提醒,我们的数组“a”如下所示:
array([1, 2, 3, 4])
现在,我们有两个数组,a和b。让我们做同样的数学运算。再说一次,这很简单,不言自明,
输入:
a + b
输出:
array([ 4, 6, 8, 10])
你可以用同样的方法进行以下操作:
a - b
a*b
a/b
a**b
另一种广泛使用的操作是,
输入:
a.dot(b)
输出:
50
什么是a.dot(b)?这是先应用元素的乘法,然后再进行累加,
1*3 + 2*4 + 3*5 + 4*6
其中数组“a”是[1,2,3,4],数组b是[3,4,5,6]。
你也可以写一些不同的语法,
np.dot(a, b)
这是一样的。输出将是50。
我们可以在多维数组中使用这个过程。我们做两个多维数组,
输入:
c = np.array([[3, 5, 1], [6, 4, 9]])
c
输出:
array([[3, 5, 1],
[6, 4, 9]])
输入:
d = np.array([[5,2], [7,9], [4, 3]])
d
输出:
array([[5, 2],
[7, 9],
[4, 3]])
我们准备好在多维数组上进行“点”运算,
输入:
c.dot(d)
输出:
array([[54, 54],
[94, 75]])
当输入为二维数组时,“点”函数的行为类似于矩阵乘法。
这意味着你只能在第一个数组的列数与第二个数组中的行数匹配时执行“点”操作。
如果第一个数组是mxn,那么第二个数组应该是nxp。
矩阵乘法还有另一个表达式,
输入:
np.matmul(c, d)
输出:
array([[54, 54],
[94, 75]])
‘np.matmul'在一维数组中不起作用
记住,这个乘法规则不适用于其他运算,如加法、减法或除法。我们需要有相同形状和大小的数组来对一个矩阵进行加法、减法或除法。
统计
Numpy也有基本的统计操作。这里有一些例子。
首先创建一个新数组。
输入:
x = np.array([1,3,4,6,-3,-2])
x.sum()
输出:
9
输入:
x.max()
输出:
6
输入:
x.min()
输出:
-3
输入:
x.mean()
输出:
1.5
输入:
x.std() # 标准差
输出:
3.2015621187164243
还有另外两个非常有用的函数,它们不是完全统计的,
输入:
x.argmin()
输出:
4
输入:
x.argmax()
输出:
3
什么是“argmin()”或“argmax()”?
“argmin()”提供数组最小元素的索引,“argmax()”返回数组最大值的索引。
数组“x”的最小元素是-3,数组“x”的最大元素是6。可以检查他们的索引是否匹配。
初始化不同类型的数组
Numpy中有很多不同的方法来初始化数组。这里我将讨论一些常用的方法:
输入:
np.arange(10)
输出:
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
这是初始化一系列数字的方法。注意它从0开始到9结束。始终排除上限。这里的上限是10。所以,它在9停止。
我们还可以添加一个数学运算:
输入:
np.arange(10)**2
输出:
array([ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81], dtype=int32)
在本例中,我们要求平方,我们得到了输出数组中0到9的平方。
我们可以用一定的间隔把一系列的数字组成一个数组。
np.arange(0, 15, 3)
输出:
array([ 0, 3, 6, 9, 12])
这里,0是下限,15是上限,3是间隔。
还有另一种方法可以提供稍微不同的序列:
输入:
np.linspace(0, 3, 15)
输出:
array([0. , 0.21428571, 0.42857143, 0.64285714, 0.85714286,
1.07142857, 1.28571429, 1.5 , 1.71428571, 1.92857143,
2.14285714, 2.35714286, 2.57142857, 2.78571429, 3. ])
这里的元素数是0,上限是3。在本例中,Numpy自动生成15个元素,这些元素的间距从0到3相等。
还有几种其他类型的数组:
输入:
np.ones((3, 4))
输出:
array([[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.]])
输入:
np.zeros((2, 3))
输出:
array([[0., 0., 0.],
[0., 0., 0.]])
你可以得到一个三维数组:
输入:
np.ones((4,3,2), dtype='int32')
输出:
array([[[1, 1],
[1, 1],
[1, 1]],[[1, 1],
[1, 1],
[1, 1]],[[1, 1],
[1, 1],
[1, 1]],[[1, 1],
[1, 1],
[1, 1]]])
这里,(4,3,2)表示4个二维数组,每个数组有3行2列。
还有另一种方法叫做full,它可以替换数组的元素:
输入:
np.full((2,2), 30)
输出:
array([[30, 30],
[30, 30]])
输入:
ar = np.array([[2,3], [4,5]])
ar
输出:
array([[2, 3],
[4, 5]])
输入:
np.full_like(ar, 4)
输出:
array([[4, 4],
[4, 4]])
还有另一种类型的矩阵称为单位矩阵:
输入:
np.identity(5)
输出:
array([[1., 0., 0., 0., 0.],
[0., 1., 0., 0., 0.],
[0., 0., 1., 0., 0.],
[0., 0., 0., 1., 0.],
[0., 0., 0., 0., 1.]])
这是一个5x5的矩阵,只有对角元素是1,其他元素都是0。
还有一种类型叫做“eye”。它的参数是矩阵的形状:
输入:
np.eye(3,3)
输出:
array([[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.]])
输入:
np.eye(3,4)
输出:
array([[1., 0., 0., 0.],
[0., 1., 0., 0.],
[0., 0., 1., 0.]])
对角线上的数字可以不同于1。
输入:
a = np.array([2,4,5])
np.diag(a)
输出:
array([[2, 0, 0],
[0, 4, 0],
[0, 0, 5]])
重新排列或重新组织数组
有不同的方法来重新排列或组织数组。
首先,做一个数组,
输入:
x = np.arange(0, 45, 3)
x
输出:
array([ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42])
我在上一节中解释了“arange”函数。让我们看看如何重塑它。
输入:
x.reshape(3, 5)
输出:
array([[ 0, 3, 6, 9, 12],
[15, 18, 21, 24, 27],
[30, 33, 36, 39, 42]])
我们传入了(3,5)。因此,它变成了一个有3行5列的二维数组。我们可以通过使用:
x.resize(3,5)
如果我们想回到原来的一维数组呢?
这是方法之一
输入:
x.ravel()
输出:
array([ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42])
看,我们找回了原来的数组!
注意另一件事。我们改变了数组的维数。数组“x”是一维数组。我们通过重塑它使它成为一个二维数组。
现在,制作另一个数组来更好地理解它。这是另一个例子。
输入:
c = np.array([4,5,6])
c
输出:
array([4, 5, 6])
这次我将使用resize。“reshape”也会这样做。为了练习调整大小,让我们在这里使用resize。
输入:
c.resize(3,1)
输出:
array([[4],
[5],
[6]])
我们提供了(3,1)作为调整大小的参数。所以它有3行1列。这是一个3x1矩阵。我们也可以有一个1x3矩阵。
输入:
c.resize(1,3)
c
输出:
array([[4, 5, 6]])
原来c是一维数组。或者现在是二维矩阵。
不要认为你只能重塑一个一维数组的大小。也可以在高维数组中执行此操作。
我举几个例子:
输入:
x = np.array([[1,2,3,4], [5,6,7,8]])
x
输出:
array([[1, 2, 3, 4],
[5, 6, 7, 8]])
现在重塑这个二维数组,
输入:
x.reshape(4,2)
x
输出:
array([[1, 2],
[3, 4],
[5, 6],
[7, 8]])
你可以使用我前面提到的“resize”来实现这一点。还有另一种方法,
输入:
y = x.reshape(4, -1)
y
输出:
array([[1, 2],
[3, 4],
[5, 6],
[7, 8]])
看起来很困惑?想象一下,你有一个巨大的数组或数据集。在重塑之前,你只知道一个维度。因此,在reshape方法中,给出了其他维的大小,剩下的numpy可以自己计算。
在上面的例子中,我传递了第一个维度4。这意味着我要让Numpy做4行。我不知道有多少列。所以我就设置参数-1。所以,它会自动生成2列。
当我们处理大数据集或数据帧时,这是一个非常有用的技巧,我们必须构建机器学习算法。
在上面的所有例子中,我们看到了如何重塑和改变尺寸。
这是改变尺寸的方法。上面的数组“y”是一个4x2矩阵。让我们再做一个2x4矩阵。
输入:
y.T
输出:
array([[1, 3, 5, 7],
[2, 4, 6, 8]])
这种方法称为转置。当你在数组或矩阵上使用转置时,它只是改变了维数。2x3矩阵变为3x2,3x6矩阵变为6x3或1x3矩阵变为3x1。
索引或切片
索引和切片是一项非常常见的日常任务。我们来举几个例子:
输入:
a = np.array([2,5,1,7,6,3,9,0,4])
输入:
a[0]
输出:
2
a[0]给出数组的第一个元素。同样,我们可以继续使用a[1],a[2],一直到整个数组。
输入:
a[3]
输出:
7
我们也可以切片,
输入:
a[1:5]
输出:
array([5, 1, 7, 6])
我们输入了[1:5]。因此,切片将从索引1开始,在索引5之前结束。记住,包括下限,排除上限。
在本文中,我不再深入讨论切片和索引。因为我已经写了另一篇文章详细解释过了。请检查一下。学好它很重要。
https://towardsdatascience.com/indexing-and-slicing-of-1d-2d-and-3d-arrays-in-numpy-e731afff0bbe
添加行或列
Numpy有几种不同的方法来添加行或列。这里有一些例子。
这次我将使用一些列表或数组。Numpy会在堆叠时自动将它们变成数组。
这里有两个列表:
x1 = [[2, 4, 3, 7], [2, 5, 3, 1]]
x2 = [1, 0, 9, 5]
现在垂直堆叠它们。
输入:
np.vstack([x1, x2])
输出:
array([[2, 4, 3, 7],
[2, 5, 3, 1],
[1, 0, 9, 5]])
你可以把它们叠成你想要的次数。
输入:
np.vstack([x1, x2, x2])
输出:
array([[2, 4, 3, 7],
[2, 5, 3, 1],
[1, 0, 9, 5],
[1, 0, 9, 5]])
让我们做一些水平堆叠。我们需要行数相同的数组。
“x1”有2行。用它做一个数组。
输入:
np.array(x1)
输出:
array([[2, 4, 3, 7],
[2, 5, 3, 1]])
生成另一个数组“x3”。
输入:
x3 = np.ones((2,3))
x3
输出:
array([[1., 1., 1.],
[1., 1., 1.]])
水平堆叠
输入:
np.hstack([x1, x3])
输出:
array([[2., 4., 3., 7., 1., 1., 1.],
[2., 5., 3., 1., 1., 1., 1.]])
连接
另一种添加列或行的方法。但与堆叠相反,这次我们需要两个相同维度的数组。记住,当我们进行垂直堆叠时,我们有一个二维和一维列表。
这是我在这个例子中的两个列表。
x1 = [[2, 4, 3, 7], [2, 5, 3, 1]]
x2 = [[1, 0, 9, 5]]
concatenate操作
输入:
np.concatenate((x1, x2), axis=0)
输出:
array([[2, 4, 3, 7],
[2, 5, 3, 1],
[1, 0, 9, 5]])
现在,水平连接。但我们需要两个行数相同的数组。
x3 = [[2,4], [7,5]]
连接x1和x3。
输入:
np.concatenate((x1, x3), axis=1)
输出:
array([[2, 4, 3, 7, 2, 4],
[2, 5, 3, 1, 7, 5]])
追加、插入、删除和排序
你可能知道这些行动的函数。
append
输入:
np.append([2,3], [[4,5], [1, 3]])
输出:
array([2, 3, 4, 5, 1, 3])
输入:
np.append([2, 3, 1], [[4, 5], [1,3]])
输出:
array([2, 3, 1, 4, 5, 1, 3]
我们在这些例子中没有提到任何轴心。所以,默认情况下,它们取轴1,或者在列方向或水平方向。现在,在垂直方向执行追加操作。
输入:
np.append([[1,3,5], [4,3,6]], [[1,2,3]], axis=0)
输出:
array([[1, 3, 5],
[4, 3, 6],
[1, 2, 3]])
Insert
这次我们将在某个位置插入一个元素。从一个新数组开始。
输入:
a = np.array([[2, 2], [3, 4], [5, 6]])
a
输出:
array([[2, 2],
[3, 4],
[5, 6]])
在数组的开头插入元素5。
输入:
np.insert(a, 0, 5)
输出:
array([5, 2, 2, 3, 4, 5, 6])
首先,理解输入。在(a,0,5)中,a是数组,0是要插入元素的位置,5是要插入的元素。
注意,插入是如何发生的。首先,二维数组a被展平成一维数组。然后在索引0处添加5。
我们也可以沿着轴插入。
输入:
np.insert(a, 0, 5, axis=1)
输出:
array([[5, 2, 2],
[5, 3, 4],
[5, 5, 6]])
看,一列5被添加到数组'a'中。我们也可以添加一行5。
输入:
np.insert(a, 0, 5, axis=0)
输出:
array([[5, 5],
[2, 2],
[3, 4],
[5, 6]])
Delete
我会像以前一样做一个新的数组。
输入:
a= np.array([[1,3,2,6], [4,1,6,7], [9, 10, 6, 3]])
a
输出:
array([[ 1, 3, 2, 6],
[ 4, 1, 6, 7],
[ 9, 10, 6, 3]])
输入:
np.delete(a, [1, 2, 5])
输出:
array([ 1, 6, 4, 6, 7, 9, 10, 6, 3])
与插入操作一样,删除操作也会使数组变平。在输入[1,2,5]中是要删除的索引列表。为了清楚地看到它,让我们展平原始数组'a'。
输入:
a.flatten()
输出:
array([ 1, 3, 2, 6, 4, 1, 6, 7, 9, 10, 6, 3])
现在检查一下,索引1、2和5的元素都被删除了。
与插入类似,我们可以删除特定的行或列。
删除列索引1。
输入:
np.delete(a, 1, 1)
输出:
array([[1, 2, 6],
[4, 6, 7],
[9, 6, 3]])
在输入(a,1,1)中,a是数组,1是要删除的列的索引,最后一个1是轴。
输入:
np.delete(a, 1, 0)
输出:
array([[ 1, 3, 2, 6],
[ 9, 10, 6, 3]])
Sort
数组“a”:
array([[ 1, 3, 2, 6],
[ 4, 1, 6, 7],
[ 9, 10, 6, 3]])
输入:
np.sort(a)
输出:
array([[ 1, 2, 3, 6],
[ 1, 4, 6, 7],
[ 3, 6, 9, 10]])
看,它是双向排列的。我们可以指定轴并按特定轴排序。
输入:
np.sort(a, axis=None)
输出:
array([ 1, 1, 2, 3, 3, 4, 6, 6, 6, 7, 9, 10])
当轴为“None”时,它展平数组并进行排序。现在,按轴0和轴1排序。
输入:
np.sort(a, axis=0)
输出:
array([[ 1, 1, 2, 3],
[ 4, 3, 6, 6],
[ 9, 10, 6, 7]])
输入:
np.sort(a, axis=1)
输出:
array([[ 1, 2, 3, 6],
[ 1, 4, 6, 7],
[ 3, 6, 9, 10]])
Flip
它确实像听起来那样。翻转数组和行。
这是该数组。
arr
输出:
array([[ 1, 2, 3, 4],
[ 5, 6, 7, 8],
[ 9, 10, 11, 12]])
现在,沿轴0和1的方向翻转该数组。
输入:
np.flip(arr, 0)
输出:
array([[ 9, 10, 11, 12],
[ 5, 6, 7, 8],
[ 1, 2, 3, 4]])
输入:
np.flip(arr, 1)
输出:
array([[ 4, 3, 2, 1],
[ 8, 7, 6, 5],
[12, 11, 10, 9]])
随机
Numpy有很好的随机数生成功能。它们在机器学习、研究或统计方面非常有用。这里有一些例子。
输入:
np.random.rand()
输出:
0.541670003513435
它生成一个介于0到1之间的数字。我们可以从这样的随机数中得到一个数组或矩阵。
输入:
np.random.rand(3)
输出:
array([0.6432591 , 0.78715203, 0.81071309])
输入:
np.random.rand(2, 3)
输出:
array([[0.91757316, 0.74438045, 0.85259742],
[0.19826903, 0.84990728, 0.48328816]])
它不一定是从0到1的数字。我们可以生成随机整数。
输入:
np.random.randint(25)
输出:
20
它产生了一个0到25范围内的随机数。我们可以指定要生成多少个数字。
输入:
np.random.randint(1, 100, 10)
输出:
array([96, 44, 90, 13, 47, 16, 9, 46, 49, 20])
在这里,我们要求Numpy生成10个介于1到100之间的数字。
现在,生成1到100范围内的3x3矩阵。
输入:
np.random.randint(1, 100, (3,3))
输出:
array([[25, 80, 42],
[95, 82, 66],
[64, 95, 55]])
你可以提供一个数组,并要求Numpy使用你提供的数组中的数字生成一个3x3矩阵,而不是一个范围。
输入:
np.random.choice([1,2,3,4,5,6,7,8,9,10], size=(3,3))
输出:
array([[ 7, 9, 2],
[ 6, 4, 6],
[ 3, 10, 6]])
另一个有用的函数是“shuffle”。让我们做一个新的数组并进行shuffle。
输入:
a = np.array([3,6,3,1,0, 11])
np.random.shuffle(a)
a
输出:
array([ 3, 0, 6, 3, 11, 1])
听着,我们有相同的元素,只是在shuffle后重新排列。
保存、加载和导入文件
我们可以将数组“arr”保存在一个文件中。
输入:
np.save('arrfile', arr)
这里,我们正在生成一个名为“arrfile”的文件来保存数组“arr”。文件将以“.npy”扩展名保存。
我们可以加载该文件并将其带回来继续使用该数组,
输入:
np.load('arrfile.npy')
输出:
array([[ 1, 2, 3, 4],
[ 5, 6, 7, 8],
[ 9, 10, 11, 12]])
我们可以使用Numpy作为数组导入CSV文件或文本文件。我在Jupyter Notebook相同的文件夹中有一个名为'Cartwheeldata.csv“,我编写了这些示例。现在,在这里导入该文件。
输入:
filedata = np.genfromtxt('Cartwheeldata.csv', delimiter=',')
filedata=filedata.astype('int32')
filedata
输出:
我在这里展示数组的一部分。因为文件很大。所以,这是关于那个文件的部分信息。
这些类型的数组在机器学习中非常有用。
结论
这就是我想在本文中分享的所有Numpy函数。Numpy是个大库。它有很多可用的方法。但是这些函数应该足够适合日常使用。
原文链接:https://towardsdatascience.com/an-ultimate-cheat-sheet-for-numpy-bb1112b0488f
欢迎关注磐创AI博客站:
http://panchuang.net/
sklearn机器学习中文官方文档:
http://sklearn123.com/
欢迎关注磐创博客资源汇总站:
http://docs.panchuang.net/