{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 基于 Numpy 的 Python 科学计算入门\n",
    "\n",
    "<div align=center>\n",
    "<img width=\"1000\" src=\"https://github.com/zhaochenyang20/zhaochenyang20.github.io/blob/master/img/profile_4.jpg?raw=true\"/>\n",
    "</div>\n",
    "<div align=center></div>\n",
    "\n",
    "**[Eren Zhao](https://zhaochenyang20.github.io/) THU CST Summer Tutorial 2022**\n",
    "\n",
    "## Intro: Why Numpy?\n",
    "\n",
    "本课将介绍通过 Python 有效导入、存储和操作内存数据的主要技巧。这个主题非常广泛, 因为数据集的来源与格式都十分丰富, 比如文档集合、图像集合、声音片段集合、数值数据集合, 等等。这些数据虽然存在明显的异构性, 但是将所有数据简单地看作数字数组非常有助于我们理解和处理数据。\n",
    "\n",
    "例如, 可以将图像（尤其是数字图像）简单地看作二维数字数组, 这些数字数组代表各区 域的像素值; 声音片段可以看作时间和强度的一维数组; 文本也可以通过各种方式转换成 数值表示, 一种可能的转换是用二进制数表示特定单词或单词对出现的频率。不管数据是何种形式, 第一步都是将这些数据转换成数值数组形式的可分析数据。\n",
    "\n",
    "正因如此, 有效地存储和操作数值数组是数据科学中绝对的基础过程。我们将详细介绍 NumPy。NumPy（Numerical Python 的简称）提供了高效存储和操作密集数据缓存的接口。在某些方面, NumPy 数组与 Python 内置的列表类型非常相似。但是随着数组在维度上变大, NumPy 数组提供了更加高效的存储和数据操作。NumPy 数组几乎是整个 Python 科学计算生态系统的核心。因此, 不管你对数据科学的哪个方面感兴趣, 花点时间学习如何有效地使用 NumPy 都是非常值得的。\n",
    "\n",
    "如果你听从前言给出的建议安装了 Anaconda, 那么你已经安装好 NumPy, 并可以使用它了。如果你是个体验派, 则可以到 NumPy 网站（http://www.numpy.org/）按照其安装指导进行安装。安装好后, 你可以导入 NumPy 并再次核实你的 NumPy 版本:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'1.22.3'"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "np.__version__"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 内容重点\n",
    "\n",
    "这份讲义是我~~多年来~~学习中用到的 numpy 的 API 的总结，不得不说非常得多，为此把它们整合为为了查阅的手册。我个人认为，以下内容是你需要清楚理解并且在学习和工作中经常提醒自己的，而其他内容都可以作为工具书来查阅：\n",
    "\n",
    "- Numpy 和 Python 的效率区别\n",
    "- Numpy 广播机制的规则\n",
    "- Numpy Fancy Index\n",
    "- 传统索引的视图问题\n",
    "\n",
    "其他的 API 基本是用惯了就记住了，而这三点是绝对的重点，也是我课程的讲课重点。其余自学都没有任何问题。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Python 数据类型\n",
    "\n",
    "要实现高效的数据驱动科学和计算，需要理解数据是如何被存储和操作的。本节将介绍在 Python 语言中数据数组是如何被处理的，并对比 NumPy 所做的改进。理解这个不同之处是理解本节其他内容的基础。\n",
    "\n",
    "Python 的用户往往被其易用性所吸引，其中一个易用之处就在于动态输入。静态类型的语言（如 C 或 Java）往往需要每一个变量都明确地声明，而动态类型的语言（例如 Python）可以跳过这个特殊规定。例如在 C 语言中，你可能会按照如下方式指定一个特殊的操作：\n",
    "\n",
    "``` c\n",
    "/* C代码 */\n",
    "int result = 0;\n",
    "for(int i=0; i<100; i++){\n",
    " result += i;\n",
    "}\n",
    "```\n",
    "而在 Python 中，同等的操作可以按照如下方式实现：\n",
    "\n",
    "``` Python\n",
    "# Python代码\n",
    "result = 0\n",
    "for i in range(100):\n",
    " result += i\n",
    "```\n",
    "注意这里最大的不同之处：在 C 语言中，每个变量的数据类型被明确地声明；而在  Python 中，类型是动态推断的。这意味着可以将任何类型的数据指定给任何变量：\n",
    "``` Python\n",
    "# Python代码\n",
    "x = 4\n",
    "x = \"four\"\n",
    "\n",
    "```\n",
    "\n",
    "这里已经将 x 变量的内容由整型转变成了字符串，而同样的操作在 C 语言中将会导致（取决于编译器设置）编译错误或其他未知的后果：\n",
    "\n",
    "``` C\n",
    "/* C代码 */\n",
    "int x = 4;\n",
    "x = \"four\"; // 编译失败\n",
    "```\n",
    "\n",
    "这种灵活性是使 Python 和其他动态类型的语言更易用的原因之一。理解这一特性如何工作是学习用 Python 有效且高效地分析数据的重要因素。但是这种类型灵活性也指出了一个事实：Python 变量不仅是它们的值，还包括了关于值的类型的一些额外信息，本节接下来的内容将进行更详细的介绍。\n",
    "\n",
    "### Python 整型不仅仅是一个整型\n",
    "\n",
    "标准的 Python 实现是用 C 语言编写的。这意味着每一个 Python 对象都是一个聪明的伪 C 语言结构体，该结构体不仅包含其值，还有其他信息。例如，当我们在 Python 中定义一个整型，例如 x = 10000 时，x 并不是一个“原生”整型，而是一个指针，指向一个 C 语言的复合结构体，结构体里包含了一些值。查看 Python 3.4 的源代码，可以发现整型（长整型）的定义，如下所示（C 语言的宏经过扩展之后）：\n",
    "```C\n",
    "struct _longobject {\n",
    " long ob_refcnt;\n",
    " PyTypeObject *ob_type;\n",
    " size_t ob_size;\n",
    " long ob_digit[1];\n",
    "};\n",
    "```\n",
    "Python 3.4 中的一个整型实际上包括 4 个部分。\n",
    "\n",
    "- ob_refcnt 是一个引用计数，它帮助 Python 默默地处理内存的分配和回收。\n",
    "- ob_type 将变量的类型编码。\n",
    "- ob_size 指定接下来的数据成员的大小。\n",
    "- ob_digit 包含我们希望 Python 变量表示的实际整型值。\n",
    "\n",
    "这意味着与 C 语言这样的编译语言中的整型相比，在 Python 中存储一个整型会有一些开销。\n",
    "\n",
    "<div align=center>\n",
    "<img width=\"500\" src=\"https://zhaochenyang20.github.io/pic/embed/6_29_1.jpg\"/>\n",
    "</div>\n",
    "<div align=center>C 整型和 Python 整型的区别，这里 PyObject_HEAD 是结构体中包含引用计数、类型编码和其他部分。</div>\n",
    "\n",
    "两者的差异在于，C 语言整型本质上是对应某个内存位置的标签，里面存储的字节会编码成整型。而 Python 的整型其实是一个指针，指向包含这个 Python 对象所有信息的某个内存位置，其中包括可以转换成整型的字节。由于 Python 的整型结构体里面还包含了大量额外的信息，所以 Python 可以自由、动态地编码。但是，Python 类型中的这些额外信息也会成为负担，在多个对象组合的结构体中尤其明显。\n",
    "\n",
    "### Python 列表不仅仅是一个列表\n",
    "设想如果使用一个包含很多 Python 对象的 Python 数据结构，会发生什么？ Python 中的标准可变多元素容器是列表。可以用如下方式创建一个整型值列表：\n",
    "\n",
    "``` Python\n",
    "In [1]: L = list(range(10))\n",
    "\n",
    "In [2]: L\n",
    "Out[2]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n",
    "```\n",
    "\n",
    "或者创建一个字符串列表：\n",
    "\n",
    "``` Python\n",
    "In [4]: L2 = [str(each) for each in L]\n",
    "\n",
    "In [5]: L2\n",
    "Out[5]: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']\n",
    "\n",
    "In [6]: type(L2[0])\n",
    "Out[6]: str\n",
    "```\n",
    "\n",
    "因为 Python 的动态类型特性，甚至可以创建一个异构的列表：\n",
    "\n",
    "``` Python\n",
    "In [7]:  L3 = [True, \"2\", 3.0, 4]\n",
    "\n",
    "In [8]: [type(item) for item in L3]\n",
    "Out[8]: [bool, str, float, int]\n",
    "```\n",
    "\n",
    "但是想拥有这种灵活性也是要付出一定代价的：为了获得这些灵活的类型，列表中的每一项必须包含各自的类型信息、引用计数和其他信息；也就是说，每一项都是一个完整的 Python 对象。来看一个特殊的例子，如果列表中的所有变量都是同一类型的，那么很多信息都会显得多余——将数据存储在固定类型的数组中应该会更高效。动态类型的列表和固定类型的（NumPy 式）数组间的区别如下图所示。\n",
    "\n",
    "<div align=center>\n",
    "<img width=\"500\" src=\"https://zhaochenyang20.github.io/pic/embed/6_29_2.jpg\"/>\n",
    "</div>\n",
    "<div align=center>numpy 列表和 Python 列表的区别。</div>\n",
    "\n",
    "在实现层面，数组基本上包含一个指向连续数据块的指针。另一方面，Python 列表包含一个指向指针块的指针，这其中的每一个指针对应一个完整的 Python 对象（如前面看到的 Python 整型）。另外，列表的优势是灵活，因为每个列表元素是一个包含数据和类型信息的完整结构体，而且列表可以用任意类型的数据填充。固定类型的 NumPy 式数组缺乏这种灵活性，但是能更有效地存储和操作数据。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 为何 numpy 会更快？\n",
    "Python 的默认实现（被称作 CPython）处理起某些操作时非常慢，一部分原因是该语言的动态性和解释性——数据类型灵活的特性决定了序列操作不能像 C 语言和 Fortran 语言一样被编译成有效的机器码。目前，有一些项目试图解决 Python 这一弱点，比较知名的包括：PyPy 项目（http://pypy.org/）。一个实时的 Python 编译实现；Cython 项目（http://cython.org），将 Python 代码转换成可编译的 C 代码；Numba 项目（http://numba.pydata.org/），将 Python 代码的片段转换成快速的 LLVM 字节码。以上这些项目都各有其优势和劣势，但是比较保守地说，这些方法中还没有一种能达到或超过标准 CPython 引擎的受欢迎程度。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们具体来展示和理解下 CPython 的缓慢——\n",
    "\n",
    "Python 的相对缓慢通常出现在很多小操作需要不断重复的时候，比如对数组的每个元素做循环操作时。假设有一个数组，我们想计算每个元素的倒数，一种直接的解决方法是："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0.16666667, 1.        , 0.25      , 0.25      , 0.125     ])"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import numpy as np\n",
    "np.random.seed(0)\n",
    "def compute_reciprocals(values):\n",
    "    output = np.empty(len(values))\n",
    "    for i in range(len(values)):\n",
    "        output[i] = 1.0 / values[i]\n",
    "    return output\n",
    "values = np.random.randint(1, 10, size=5)\n",
    "compute_reciprocals(values)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这种实现方式可能对于有 C 语言或 Java 背景的人来说非常自然，但是如果测试一个很大量的输入数据运行上述代码的时间，这一操作将非常耗时，并且是超出意料的慢！我们用 IPython 的 %timeit 魔法函数来测量："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1.84 s ± 17.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n",
      "1.82 s ± 34.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n"
     ]
    }
   ],
   "source": [
    "big_array_numpy = np.random.randint(1, 100, size=1000000)\n",
    "big_array_list = list(big_array_numpy)\n",
    "%timeit compute_reciprocals(big_array_numpy)\n",
    "%timeit compute_reciprocals(big_array_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "完成百万次上述操作并存储结果花了几秒钟的时间！在手机都以 Giga-FLOPS（即每秒十亿次浮点运算，一个有趣的对比是当年阿姆斯特朗飞到月球所需要的算力基本等于一部智能手机的算力）为单位计算处理速度时，上面的处理结果所花费的时间确实是不合时宜的慢。事实上，这里的处理瓶颈并不是运算本身，而是 CPython 在每次循环时必须做数据类型的检查和函数的调度。每次进行倒数运算时，Python 首先检查对象的类型，并且动态查找可以使用该数据类型的正确函数。如果我们在编译代码时进行这样的操作，那么就能在代码执行之前知晓类型的声明，结果的计算也会更加有效率。\n",
    "\n",
    "在上方的例子里，你可能看不出 numpy 的明显优势，但是毕竟这还是在显示地运行 for 循环，而 numpy 的快速主要体现在有着遍历性质的通用函数对 numpy 数组的巨幅加成上。\n",
    "\n",
    "先扯到这里，我们开始正式入门。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 创建和生成\n",
    "\n",
    "本节主要介绍 array 的创建和生成。为什么会把这个放在最前面呢？主要有以下两个方面原因：\n",
    "\n",
    "- 在实际工作过程中，我们时不时需要验证或查看 array 相关的 API 或互操作。\n",
    "- 有时候在使用 sklearn，matplotlib，PyTorch，Tensorflow 等工具时也需要一些简单的数据进行实验。\n",
    "\n",
    "所以，先学会如何快速拿到一个 array 是有很多益处的。本节我们主要介绍以下几种常用的创建方式：\n",
    "\n",
    "- 使用列表或元组\n",
    "- 使用 arange\n",
    "- 使用 linspace/logspace\n",
    "- 使用 ones/zeros\n",
    "- 使用 random\n",
    "- 从文件读取\n",
    "\n",
    "其中，最常用的一般是 linspace/logspace 和 random，前者常常用在画坐标轴上，后者则用于生成「模拟数据」。举例来说，当我们需要画一个函数的图像时，X 往往使用 linspace 生成，然后使用函数公式求得 Y，再 plot；当我们需要构造一些输入（比如 X）或中间输入（比如 Embedding、hidden state）时，random 会异常方便。\n",
    "\n",
    "### 从 python 列表或元组创建\n",
    "\n",
    "⭐⭐\n",
    "\n",
    "重点掌握传入 list 创建一个 array 即可：`np.array(list)`\n",
    "\n",
    "⚠️ 需要注意的是：「数据类型」。如果您足够仔细的话，可以发现下面第二组代码第 2 个数字是「小数」（注：Python 中 1. == 1.0），而 array 是要保证每个元素类型相同的，所以会帮您把 array 转为一个 float 的类型。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([1, 2, 3])"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 一个 list\n",
    "np.array([1,2,3])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[1., 2., 3.],\n",
       "       [4., 5., 6.]])"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 二维（多维类似）\n",
    "# 注意，有一个小数\n",
    "np.array([[1, 2., 3], [4, 5, 6]])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([1., 2., 3.], dtype=float16)"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 你也可以指定数据类型\n",
    "np.array([1, 2, 3], dtype=np.float16)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[1, 2, 3],\n",
       "       [4, 5, 6]], dtype=int32)"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 如果指定了 dtype，输入的值都会被转为对应的类型，而且不会四舍五入\n",
    "lst = [\n",
    "    [1, 2, 3],\n",
    "    [4, 5, 6.8]\n",
    "]\n",
    "np.array(lst, dtype=np.int32)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([1.1, 2.2])"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 一个 tuple\n",
    "np.asarray((1.1, 2.2))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[1.1, 2.2, 3.3],\n",
       "       [4.4, 5.5, 6.6]])"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# tuple，一般用 list 就好，不需要使用 tuple\n",
    "np.array([(1.1, 2.2, 3.3), (4.4, 5.5, 6.6)])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "至于 asarray 和 array 的区别：\n",
    "\n",
    "array 和 asarray 都可以将结构数据转化为 ndarray，但是主要区别就是当数据源是 ndarray 时，array 会 copy 出一个副本，占用新的内存，但 asarray 不会。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[1 2 3]\n",
      "[ 1 10  3]\n"
     ]
    }
   ],
   "source": [
    "arr1 = np.array([1, 2, 3])\n",
    "arr2 = np.array(arr1)\n",
    "arr3 = np.asarray(arr1)\n",
    "arr2[1] = 10\n",
    "print(arr1)\n",
    "arr3[1] = 10\n",
    "print(arr1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 使用 arange 生成\n",
    "\n",
    "⭐⭐\n",
    "\n",
    "range 是 Python 内置的整数序列生成器，arange 是 numpy 的，效果类似，会生成一维的向量。我们偶尔会需要使用这种方式来构造 array，比如：\n",
    "\n",
    "需要创建一个连续一维向量作为输入（比如编码位置时可以使用）需要观察筛选、抽样的结果时，有序的 array 一般更加容易观察\n",
    "\n",
    "⚠️ 需要注意的是：在 reshape 时，目标的 shape 需要的元素数量一定要和原始的元素数量相等。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 0,  1,  2,  3],\n",
       "       [ 4,  5,  6,  7],\n",
       "       [ 8,  9, 10, 11]])"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.arange(12).reshape(3, 4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 0.,  1.,  2.],\n",
       "       [ 3.,  4.,  5.],\n",
       "       [ 6.,  7.,  8.],\n",
       "       [ 9., 10., 11.]])"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 注意，是小数\n",
    "np.arange(12.0).reshape(4, 3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[[100, 102],\n",
       "        [104, 106]],\n",
       "\n",
       "       [[108, 110],\n",
       "        [112, 114]],\n",
       "\n",
       "       [[116, 118],\n",
       "        [120, 122]]])"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.arange(100, 124, 2).reshape(3, 2, 2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "ename": "ValueError",
     "evalue": "cannot reshape array of size 12 into shape (2,3,4)",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mValueError\u001b[0m                                Traceback (most recent call last)",
      "\u001b[1;32m/Users/zhaochen20/Desktop/Python 科学计算.ipynb Cell 23\u001b[0m in \u001b[0;36m<cell line: 2>\u001b[0;34m()\u001b[0m\n\u001b[1;32m      <a href='vscode-notebook-cell:/Users/zhaochen20/Desktop/Python%20%E7%A7%91%E5%AD%A6%E8%AE%A1%E7%AE%97.ipynb#ch0000024?line=0'>1</a>\u001b[0m \u001b[39m# shape size 相乘要和生成的元素数量一致\u001b[39;00m\n\u001b[0;32m----> <a href='vscode-notebook-cell:/Users/zhaochen20/Desktop/Python%20%E7%A7%91%E5%AD%A6%E8%AE%A1%E7%AE%97.ipynb#ch0000024?line=1'>2</a>\u001b[0m np\u001b[39m.\u001b[39;49marange(\u001b[39m100.\u001b[39;49m, \u001b[39m124.\u001b[39;49m, \u001b[39m2\u001b[39;49m)\u001b[39m.\u001b[39;49mreshape(\u001b[39m2\u001b[39;49m,\u001b[39m3\u001b[39;49m,\u001b[39m4\u001b[39;49m)\n",
      "\u001b[0;31mValueError\u001b[0m: cannot reshape array of size 12 into shape (2,3,4)"
     ]
    }
   ],
   "source": [
    "# shape size 相乘要和生成的元素数量一致\n",
    "np.arange(100., 124., 2).reshape(2,3,4)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 使用 linspace/logspace 生成\n",
    "\n",
    "⭐⭐⭐\n",
    "\n",
    "OK，这是我们遇到的第一个比较重要的 API，前者需要传入 3 个参数：开头，结尾，数量；后者需要额外传入一个 base，它默认是 10。\n",
    "\n",
    "⚠️ 需要注意的是：第三个参数并**不是**步长。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[[0.]\n",
      "  [1.]\n",
      "  [2.]\n",
      "  [3.]\n",
      "  [4.]]\n",
      "\n",
      " [[5.]\n",
      "  [6.]\n",
      "  [7.]\n",
      "  [8.]\n",
      "  [9.]]]\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "array([[[0., 1., 2., 3., 4.],\n",
       "        [5., 6., 7., 8., 9.]]])"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 线性\n",
    "print(np.linspace(0, 9, 10).reshape(2, 5, 1))\n",
    "np.linspace(0, 9, 10).reshape(1, 2, 5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0. , 1.8, 3.6],\n",
       "       [5.4, 7.2, 9. ]])"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.linspace(0, 9, 6).reshape(2, 3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "接下来是 logspace，注意其意义就是按照[指数线性](https://zhaochenyang20.github.io/2022/01/08/%E9%9A%8F%E7%AC%94/%E5%BF%83%E5%BF%83%E5%BF%B5%E5%BF%B5/%E9%9F%B3%E4%B9%90%E4%B8%AD%E7%9A%84%E6%95%B0%E7%90%86%E9%80%BB%E8%BE%91/)来生成数据。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[1.00000000e+00 6.04964746e+00 3.65982344e+01 2.21406416e+02\n",
      " 1.33943076e+03 8.10308393e+03]\n",
      "[0.  1.8 3.6 5.4 7.2 9. ]\n",
      "[1.00000000e+00 6.30957344e+01 3.98107171e+03 2.51188643e+05\n",
      " 1.58489319e+07 1.00000000e+09]\n",
      "[0.  1.8 3.6 5.4 7.2 9. ]\n"
     ]
    }
   ],
   "source": [
    "# 指数 base 默认为 10\n",
    "print(np.logspace(0, 9, 6, base=np.e))\n",
    "print(np.log(np.logspace(0, 9, 6, base=np.e)))\n",
    "print(np.logspace(0, 9, 6, base=10))\n",
    "print(np.log10(np.logspace(0, 9, 6, base=10)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "下面我们更进一步看一下："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "N = 20\n",
    "x = np.arange(N)\n",
    "y1 = np.linspace(0, 10, N) * 100\n",
    "y2 = np.logspace(0, 10, N, base=2)\n",
    "\n",
    "plt.plot(x, y2, '*');\n",
    "plt.plot(x, y1, 'o');\n",
    "#! 划线\n",
    "plt.plot(x, y2);\n",
    "plt.plot(x, y1);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 33,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 检查每个元素是否为 True\n",
    "# base 的 指数为 linspace 得到的就是 logspace\n",
    "N = 20\n",
    "y2 = np.logspace(0, 10, N, base=2)\n",
    "np.alltrue(2 ** np.linspace(0, 10, N)  == y2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ">⚠️ 补充：关于 array 的条件判断"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([False, False,  True])"
      ]
     },
     "execution_count": 34,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 不能直接用 if 判断 array 是否符合某个条件\n",
    "arr = np.array([1, 2, 3])\n",
    "cond1 = arr > 2\n",
    "cond1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "ename": "ValueError",
     "evalue": "The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mValueError\u001b[0m                                Traceback (most recent call last)",
      "\u001b[1;32m/Users/zhaochen20/Desktop/Python 科学计算.ipynb Cell 34\u001b[0m in \u001b[0;36m<cell line: 1>\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> <a href='vscode-notebook-cell:/Users/zhaochen20/Desktop/Python%20%E7%A7%91%E5%AD%A6%E8%AE%A1%E7%AE%97.ipynb#ch0000036?line=0'>1</a>\u001b[0m \u001b[39mif\u001b[39;00m cond1:\n\u001b[1;32m      <a href='vscode-notebook-cell:/Users/zhaochen20/Desktop/Python%20%E7%A7%91%E5%AD%A6%E8%AE%A1%E7%AE%97.ipynb#ch0000036?line=1'>2</a>\u001b[0m     \u001b[39mprint\u001b[39m(\u001b[39m\"\u001b[39m\u001b[39mforbid\u001b[39m\u001b[39m\"\u001b[39m)\n",
      "\u001b[0;31mValueError\u001b[0m: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()"
     ]
    }
   ],
   "source": [
    "if cond1:\n",
    "    print(\"forbid\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [
    {
     "ename": "ValueError",
     "evalue": "The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mValueError\u001b[0m                                Traceback (most recent call last)",
      "\u001b[1;32m/Users/zhaochen20/Desktop/Python 科学计算.ipynb Cell 35\u001b[0m in \u001b[0;36m<cell line: 4>\u001b[0;34m()\u001b[0m\n\u001b[1;32m      <a href='vscode-notebook-cell:/Users/zhaochen20/Desktop/Python%20%E7%A7%91%E5%AD%A6%E8%AE%A1%E7%AE%97.ipynb#ch0000037?line=1'>2</a>\u001b[0m arr \u001b[39m=\u001b[39m np\u001b[39m.\u001b[39marray([\u001b[39m1\u001b[39m, \u001b[39m2\u001b[39m, \u001b[39m3\u001b[39m])\n\u001b[1;32m      <a href='vscode-notebook-cell:/Users/zhaochen20/Desktop/Python%20%E7%A7%91%E5%AD%A6%E8%AE%A1%E7%AE%97.ipynb#ch0000037?line=2'>3</a>\u001b[0m cond2 \u001b[39m=\u001b[39m arr \u001b[39m>\u001b[39m \u001b[39m0\u001b[39m\n\u001b[0;32m----> <a href='vscode-notebook-cell:/Users/zhaochen20/Desktop/Python%20%E7%A7%91%E5%AD%A6%E8%AE%A1%E7%AE%97.ipynb#ch0000037?line=3'>4</a>\u001b[0m \u001b[39mif\u001b[39;00m cond2:\n\u001b[1;32m      <a href='vscode-notebook-cell:/Users/zhaochen20/Desktop/Python%20%E7%A7%91%E5%AD%A6%E8%AE%A1%E7%AE%97.ipynb#ch0000037?line=4'>5</a>\u001b[0m     \u001b[39mprint\u001b[39m(\u001b[39m\"\u001b[39m\u001b[39mforbid\u001b[39m\u001b[39m\"\u001b[39m)\n",
      "\u001b[0;31mValueError\u001b[0m: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()"
     ]
    }
   ],
   "source": [
    "# 即便你全是 True 它也不行\n",
    "arr = np.array([1, 2, 3])\n",
    "cond2 = arr > 0\n",
    "if cond2:\n",
    "    print(\"forbid\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "if any\n"
     ]
    }
   ],
   "source": [
    "# 只能用 any 或 all，这个很容易犯错，请务必注意。\n",
    "if cond1.any():\n",
    "    print(\"if any\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "all conform\n"
     ]
    }
   ],
   "source": [
    "if cond2.all():\n",
    "    print(\"all conform\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 使用 ones/zeros 创建\n",
    "\n",
    "⭐\n",
    "\n",
    "创建全 1/0 array 的快捷方式。需要注意的是 `np.zeros_like` 或 `np.ones_like`，二者可以快速生成给定 array 一样 shape 的 0 或 1 向量，这在需要 Mask 某些位置时可能会用到。\n",
    "\n",
    "⚠️ 需要注意的是：创建出来的 array 默认是 float 类型。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([1., 1., 1.])"
      ]
     },
     "execution_count": 39,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.ones(3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[1., 1., 1.],\n",
       "       [1., 1., 1.]])"
      ]
     },
     "execution_count": 40,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.ones((2, 3))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[[0., 0., 0., 0.],\n",
       "        [0., 0., 0., 0.],\n",
       "        [0., 0., 0., 0.]],\n",
       "\n",
       "       [[0., 0., 0., 0.],\n",
       "        [0., 0., 0., 0.],\n",
       "        [0., 0., 0., 0.]]])"
      ]
     },
     "execution_count": 41,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.zeros((2, 3, 4))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[[0. 0. 0.]\n",
      "  [0. 0. 0.]\n",
      "  [0. 0. 0.]]\n",
      "\n",
      " [[0. 0. 0.]\n",
      "  [0. 0. 0.]\n",
      "  [0. 0. 0.]]]\n"
     ]
    }
   ],
   "source": [
    "# 像给定向量那样的 0 向量（ones_like 是 1 向量）\n",
    "print(np.zeros_like(np.ones((2, 3, 3))))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 使用 random 生成\n",
    "\n",
    "⭐⭐⭐⭐⭐\n",
    "\n",
    "如果要在这一节选一个最重要的 API，那一定是 `random` 无疑了，这里我们只介绍几个比较常用的「生产」数据相关的 API。它们经常用于随机生成训练或测试数据，神经网络初始化等。\n",
    "\n",
    "⚠️ 需要注意的是：这里我们统一推荐使用新的 API 方式创建，即通过 `np.random.default_rng()` 先生成 `Generator`，然后再在此基础上生成各种分布的数据（记忆更加简便清晰）。不过我们依然会介绍就的 API 用法，因为很多代码中使用的还是旧的，您可以混个眼熟。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0.54422614, 0.7771694 , 0.17419901],\n",
       "       [0.70917992, 0.44169007, 0.24421882]])"
      ]
     },
     "execution_count": 44,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 0-1 连续均匀分布\n",
    "np.random.rand(2, 3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.5064066705679392"
      ]
     },
     "execution_count": 45,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 单个数\n",
    "np.random.rand()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0.60052534, 0.62961777],\n",
       "       [0.6097588 , 0.98259692],\n",
       "       [0.81380663, 0.90393871]])"
      ]
     },
     "execution_count": 46,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 0-1 连续均匀分布\n",
    "np.random.random((3, 2))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[-0.70988965,  0.61189297, -0.0014294 ],\n",
       "       [ 0.16404608, -0.99240214, -0.26983641]])"
      ]
     },
     "execution_count": 47,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 指定上下界的连续均匀分布\n",
    "np.random.uniform(-1, 1, (2, 3))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Generator(PCG64) at 0x7F7908077580"
      ]
     },
     "execution_count": 49,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 上面两个的区别是 shape 的输入方式不同，无伤大雅了\n",
    "# 不过从 1.17 版本后推荐这样使用（以后大家可以用新的方法）\n",
    "# rng 是个 Generator，可用于生成各种分布\n",
    "#! 42 是随机数种子，众所周知，计算机是完全不存在随机的，指定相同的随机数种子，我们会生成相同的随机结果\n",
    "rng = np.random.default_rng(42)\n",
    "rng"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0.77395605, 0.43887844, 0.85859792],\n",
       "       [0.69736803, 0.09417735, 0.97562235]])"
      ]
     },
     "execution_count": 50,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 推荐的连续均匀分布用法\n",
    "rng.random((2, 3))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0.7611397 , 0.78606431, 0.12811363],\n",
       "       [0.45038594, 0.37079802, 0.92676499]])"
      ]
     },
     "execution_count": 51,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 可以指定上下界，所以更加推荐这种用法\n",
    "rng.uniform(0, 1, (2, 3))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[[ 7 12 27 48]\n",
      "  [58 98 27  1]\n",
      "  [57 45  5 68]]\n",
      "\n",
      " [[28 19 15 42]\n",
      "  [28 93  3 32]\n",
      "  [58 89 94 32]]]\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "array([[[67, 77, 75, 19],\n",
       "        [36, 46, 49,  4],\n",
       "        [54, 15, 74, 68]],\n",
       "\n",
       "       [[92, 74, 36, 96],\n",
       "        [41, 32, 90, 37],\n",
       "        [ 7, 46, 79, 18]]])"
      ]
     },
     "execution_count": 53,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 随机整数（离散均匀分布），不超过给定的值（10）\n",
    "print(np.random.randint(100, size=(2, 3, 4)))\n",
    "rng.integers(100, size=(2, 3, 4))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[8, 4, 7],\n",
       "       [4, 5, 3]])"
      ]
     },
     "execution_count": 54,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 随机整数（离散均匀分布），指定上下界和 shape\n",
    "np.random.randint(0, 10, (2, 3))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([4, 1])"
      ]
     },
     "execution_count": 55,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 上面推荐的方法，指定大小和上界\n",
    "rng.integers(10, size=2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[6, 4, 3],\n",
       "       [2, 5, 6]])"
      ]
     },
     "execution_count": 56,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 上面推荐的方法，指定上下界\n",
    "rng.integers(0, 10, (2, 3))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 1.00598973, -2.32839773,  0.8845468 , -0.01272806],\n",
       "       [ 0.49565494, -1.40539762,  1.79185433,  0.36375782]])"
      ]
     },
     "execution_count": 57,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 标准正态分布\n",
    "np.random.randn(2, 4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 0.74325417,  0.54315427, -0.66550971,  0.23216132],\n",
       "       [ 0.11668581,  0.2186886 ,  0.87142878,  0.22359555]])"
      ]
     },
     "execution_count": 58,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 上面推荐的标准正态分布用法\n",
    "rng.standard_normal((2, 4))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[-0.9099195 ,  0.40175023,  0.2742747 , -0.76010006,  0.33305447],\n",
       "       [ 0.36653939,  0.40263391,  2.12750154,  0.45131352, -0.5574691 ],\n",
       "       [-0.84153653, -0.09624566,  0.83520801,  0.02640151,  0.65957284]])"
      ]
     },
     "execution_count": 59,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 高斯分布\n",
    "np.random.normal(0, 1, (3, 5))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 0.67891356,  0.06757907,  0.2891194 ,  0.63128823, -1.45715582],\n",
       "       [-0.31967122, -0.47037265, -0.63887785, -0.27514225,  1.49494131],\n",
       "       [-0.86583112,  0.96827835, -1.68286977, -0.33488503,  0.16275307]])"
      ]
     },
     "execution_count": 60,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 上面推荐的高斯分布用法\n",
    "rng.normal(0, 1, (3, 5))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "总之，一般会用的就是2个分布：均匀分布和正态（高斯）分布。另外，`size` 可以指定 shape。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([5, 5, 0, 5, 7])"
      ]
     },
     "execution_count": 61,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 离散均匀分布\n",
    "rng.integers(low=0, high=10, size=5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[-0.34872507, -0.46235179,  0.85797588],\n",
       "       [-0.19130432, -1.27568632, -1.13328721]])"
      ]
     },
     "execution_count": 62,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 正态（高斯）分布\n",
    "rng.normal(loc=0.0, scale=1.0, size=(2, 3))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 63,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0.58302742, 2.81383892, 2.93593758, 6.61916515, 5.57032152])"
      ]
     },
     "execution_count": 63,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 连续均匀分布\n",
    "rng.uniform(low=0, high=10, size=5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 从文件读取\n",
    "\n",
    "⭐⭐⭐⭐⭐\n",
    "\n",
    "\n",
    "这小节主要用于加载实现存储好的权重参数或预处理好的数据集，有时候会比较方便，比如训练好的模型参数加载到内存里用来提供推理服务，或者耗时很久的预处理数据直接存起来，多次实验时不需要重新处理。\n",
    "\n",
    "⚠️ 需要注意的是：存储时不需要写文件名后缀，会自动添加。\n",
    "在[人智导](https://github.com/zhaochenyang20/IAI_2022)中可以优雅地使用，但是请自行学习 `allow_pickle` 参数，此处给出[输入法大作业](https://github.com/zhaochenyang20/IAI_2022/blob/main/homework/input-method/src/pinyin.py)里的一个例子：\n",
    "``` Python\n",
    "dictionary = np.load(Path.cwd() / \"dictionary.npz\", allow_pickle=True)[\"dic\"].item()\n",
    "one, two, three = [Counter(each[1].item()) for each in np.load(Path.cwd() / \"full_set.npz\", allow_pickle=True).items()]\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 直接将给定矩阵存为 a.npy\n",
    "np.save('./a', np.array([[1, 2, 3], [4, 5, 6]]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 89,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 可以将多个矩阵存在一起，名为 `b.npz`\n",
    "np.savez(\"./b\", a=np.arange(12).reshape(3, 4), 字典=np.arange(12.).reshape(4, 3))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 79,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 和上一个一样，只是压缩了\n",
    "np.savez_compressed(\"./c\", a=np.arange(12).reshape(3, 4), c=np.arange(12.).reshape(4, 3))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 80,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[1, 2, 3],\n",
       "       [4, 5, 6]])"
      ]
     },
     "execution_count": 80,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 加载单个 array\n",
    "np.load(\"./a.npy\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 91,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 加载多个，可以像字典那样取出对应的 array\n",
    "arr = np.load(\"./b.npz\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 92,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 0.,  1.,  2.],\n",
       "       [ 3.,  4.,  5.],\n",
       "       [ 6.,  7.,  8.],\n",
       "       [ 9., 10., 11.]])"
      ]
     },
     "execution_count": 92,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 可以直接使用 npz、npb 的名称进行加载\n",
    "arr[\"字典\"]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 93,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "a\n",
      "字典\n"
     ]
    }
   ],
   "source": [
    "for key in arr:\n",
    "    print(key)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 统计和属性\n",
    "\n",
    "本节我们从 array 的基本统计属性入手，对刚刚创建的 array 进一步的了解。主要包括以下几个方面：\n",
    "\n",
    "- 尺寸相关\n",
    "- 最大、最小、中位、分位值\n",
    "- 平均、求和、标准差等\n",
    "\n",
    "都是描述性统计相关的指标，对于我们从整体了解一个 array 很有帮助。其中，用到最多的是尺寸相关的「`shape`」，最大、最小值，平均值、求和等。\n",
    "\n",
    "本节的内容非常简单，您只需要特别关注（记住）两个重要的特性：\n",
    "\n",
    "- 按维度（指定 axis）求结果。一般 0 表示列 1 表示行，可以用「**沿着行/列操作**」这样理解，不确定时可以拿个例子试一下。\n",
    "- 计算后保持维度（`keepdims=True`）\n",
    "\n",
    "\n",
    "**另外，为了便于操作，我们使用一个随机生成的 array 作为操作对象；同时，我们指定了 seed，这样每次运行，每个人看到的结果都是一样的。一般我们在训练模型时，往往需要指定 seed，这样才能在「同等条件」下进行调参。**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 94,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0.77395605, 0.43887844, 0.85859792, 0.69736803],\n",
       "       [0.09417735, 0.97562235, 0.7611397 , 0.78606431],\n",
       "       [0.12811363, 0.45038594, 0.37079802, 0.92676499]])"
      ]
     },
     "execution_count": 94,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "#  先创建一个 Generator\n",
    "rng = np.random.default_rng(seed=42)\n",
    "#  再生成一个均匀分布\n",
    "arr = rng.uniform(0, 1, (3, 4))\n",
    "arr"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 尺寸相关\n",
    "\n",
    "⭐⭐\n",
    "\n",
    "这一小节主要包括：维度、形状和数据量，其中形状 `shape` 我们用到的最多。\n",
    "\n",
    "⚠️ 需要注意的是：size 不是 shape。而 ndim 表示有几个维度。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 96,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "2"
      ]
     },
     "execution_count": 96,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 维度，array 是二维的（两个维度）\n",
    "arr.ndim"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### `np.shape`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 97,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(3, 4)"
      ]
     },
     "execution_count": 97,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 形状，返回一个 Tuple\n",
    "arr.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 98,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "12"
      ]
     },
     "execution_count": 98,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 数据量\n",
    "arr.size"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 最值分位\n",
    "\n",
    "⭐⭐⭐\n",
    "\n",
    "这一小节主要包括：最大值、最小值、中位数、其他分位数，其中『**最大值和最小值**』我们平时用到的最多。\n",
    "\n",
    "⚠️ 需要注意的是：分位数可以是 0-1 的任意小数（表示对应分位），而且分位数并不一定在原始的 array 中。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 99,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.9756223516367559"
      ]
     },
     "execution_count": 99,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 所有元素中最大的\n",
    "arr.max()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 100,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0.77395605, 0.97562235, 0.85859792, 0.92676499])"
      ]
     },
     "execution_count": 100,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 按维度（列）最大值\n",
    "arr.max(axis=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 101,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0.85859792, 0.97562235, 0.92676499])"
      ]
     },
     "execution_count": 101,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 同理，按行\n",
    "arr.max(axis=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 102,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0.43887844],\n",
       "       [0.09417735],\n",
       "       [0.12811363]])"
      ]
     },
     "execution_count": 102,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 是否保持原来的维度\n",
    "# 这个需要特别注意下，很多深度学习模型中都需要保持原有的维度进行后续计算\n",
    "# arr.min 的维度是 (3,1)，array 的 shape 是 (3,4)，按行，同时保持了行的维度\n",
    "arr.min(axis=1, keepdims=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 103,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0.09417735, 0.43887844, 0.37079802, 0.69736803]])"
      ]
     },
     "execution_count": 103,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 保持维度：（1，4），原始array是（3，4）\n",
    "arr.min(axis=0, keepdims=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 104,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0.09417735, 0.43887844, 0.37079802, 0.69736803])"
      ]
     },
     "execution_count": 104,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 一维了\n",
    "arr.min(axis=0, keepdims=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 105,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.7292538655248584"
      ]
     },
     "execution_count": 105,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 中位数\n",
    "# 其他用法和 max，min 是一样的\n",
    "np.median(arr)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 106,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0.73566204],\n",
       "       [0.773602  ],\n",
       "       [0.41059198]])"
      ]
     },
     "execution_count": 106,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 分位数，注意，分位数可以是 0-1 之间的任何数字（分位）\n",
    "# 如果是 1/2 分位，那正好是中位数\n",
    "np.quantile(arr, q=1/2, axis=1, keepdims=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 平均求和标准差\n",
    "\n",
    "⭐⭐⭐\n",
    "\n",
    "这一小节主要包括：平均值、累计求和、方差、标准差等进一步的统计指标。其中使用最多的是「平均值」。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 107,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.6051555606435642"
      ]
     },
     "execution_count": 107,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 平均值\n",
    "np.average(arr)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 108,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0.33208234, 0.62162891, 0.66351188, 0.80339911])"
      ]
     },
     "execution_count": 108,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 按维度平均（列）\n",
    "np.average(arr, axis=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 109,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0.33208234, 0.62162891, 0.66351188, 0.80339911])"
      ]
     },
     "execution_count": 109,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 另一个计算平均值的 API\n",
    "# 它与 average 的主要区别是，np.average 可以指定权重，即可以用于计算加权平均\n",
    "# 一般建议使用 average，忘掉 mean 吧！\n",
    "np.mean(arr, axis=0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### `np.sum`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 110,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([2.76880044, 2.61700371, 1.87606258])"
      ]
     },
     "execution_count": 110,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 求和，不多说了，类似\n",
    "np.sum(arr, axis=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 111,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[2.76880044],\n",
       "       [2.61700371],\n",
       "       [1.87606258]])"
      ]
     },
     "execution_count": 111,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.sum(arr, axis=1, keepdims=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 112,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0.77395605, 0.43887844, 0.85859792, 0.69736803],\n",
       "       [0.8681334 , 1.41450079, 1.61973762, 1.48343233],\n",
       "       [0.99624703, 1.86488673, 1.99053565, 2.41019732]])"
      ]
     },
     "execution_count": 112,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 按列累计求和\n",
    "np.cumsum(arr, axis=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 113,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0.77395605, 1.21283449, 2.07143241, 2.76880044],\n",
       "       [0.09417735, 1.0697997 , 1.8309394 , 2.61700371],\n",
       "       [0.12811363, 0.57849957, 0.94929759, 1.87606258]])"
      ]
     },
     "execution_count": 113,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 按行累计求和\n",
    "np.cumsum(arr, axis=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 114,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.28783096517727075"
      ]
     },
     "execution_count": 114,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 标准差，用法类似\n",
    "np.std(arr)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 115,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0.3127589 , 0.25035525, 0.21076935, 0.09444968])"
      ]
     },
     "execution_count": 115,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 按列求标准差\n",
    "np.std(arr, axis=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 116,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0.02464271, 0.1114405 , 0.0839356 ])"
      ]
     },
     "execution_count": 116,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 方差\n",
    "np.var(arr, axis=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 形状和转换\n",
    "\n",
    "array 大多数情况下都是以多维的形式出现的，一般对超过二维的多维 array 称为「张量」，二维矩阵，一维向量。因为多维度，所以自然而然地涉及到形状的改变和转换，可以算是张量最基础的「操作」了。\n",
    "\n",
    "本节我们主要涉及以下三个方面：\n",
    "\n",
    "- 改变形状\n",
    "- 反序\n",
    "- 转置\n",
    "\n",
    "其中，改变形状和转置都非常常用，我们建议您熟练掌握。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 改变形状\n",
    "\n",
    "⭐⭐⭐⭐⭐\n",
    "\n",
    "这小节里面的 API 使用非常高频，尤其是扩展 1 维度的 `expand_dims` 和去除 1 维度的 `squeeze`，您未来会在很多神经网络架构中看到这俩货的身影。\n",
    "\n",
    "⚠️ 需要注意的是：无论是扩展还是缩减，多或少的 shape 都是 1，`squeeze` 时如果指定维度，则该维度 shape 必须是 1。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 118,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[[ 9 77]\n",
      "  [65 44]\n",
      "  [43 86]\n",
      "  [ 9 70]]\n",
      "\n",
      " [[20 10]\n",
      "  [53 97]\n",
      "  [73 76]\n",
      "  [72 78]]\n",
      "\n",
      " [[51 13]\n",
      "  [84 45]\n",
      "  [50 37]\n",
      "  [19 92]]]\n",
      "arr[0]\n",
      "[[ 9 77]\n",
      " [65 44]\n",
      " [43 86]\n",
      " [ 9 70]]\n",
      "arr[:, 0]\n",
      "[[ 9 77]\n",
      " [20 10]\n",
      " [51 13]]\n",
      "arr[:, :, 0]\n",
      "[[ 9 65 43  9]\n",
      " [20 53 73 72]\n",
      " [51 84 50 19]]\n"
     ]
    }
   ],
   "source": [
    "# 换个整数的随机 array\n",
    "rng = np.random.default_rng(seed=42)\n",
    "arr = rng.integers(1, 100, (3, 4, 2))\n",
    "print(arr)\n",
    "print(\"arr[0]\")\n",
    "print(arr[0])\n",
    "print(\"arr[:, 0]\")\n",
    "print(arr[:, 0])\n",
    "print(\"arr[:, :, 0]\")\n",
    "print(arr[:, :, 0])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 120,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[ 9 77 65 44 43 86  9 70 20 10 53 97 73 76 72 78 51 13 84 45 50 37 19 92]\n",
      "(24,)\n"
     ]
    }
   ],
   "source": [
    "# 有时候您可能需要将多维 array 打平\n",
    "arr = arr.ravel()\n",
    "print(arr)\n",
    "print(arr.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### `np.expand_dims`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 124,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[92 74 37 96]\n",
      " [41 33 90 37]\n",
      " [ 8 47 79 19]]\n",
      "----------------------------------------------------\n",
      "[92 74 37 96]\n",
      "----------------------------------------------------\n",
      "[92 41  8]\n",
      "----------------------------------------------------\n",
      "(3, 4)\n",
      "----------------------------------------------------\n",
      "(1, 3, 4)\n",
      "----------------------------------------------------\n",
      "[[92 74 37 96]\n",
      " [41 33 90 37]\n",
      " [ 8 47 79 19]]\n",
      "----------------------------------------------------\n",
      "[[92 74 37 96]]\n",
      "----------------------------------------------------\n",
      "[[[92 74 37 96]\n",
      "  [41 33 90 37]\n",
      "  [ 8 47 79 19]]]\n",
      "----------------------------------------------------\n"
     ]
    }
   ],
   "source": [
    "#### 扩展 1 个维度，需要（必须）指定维度\n",
    "# 其实就是多嵌套了一下\n",
    "arr = rng.integers(1, 100, (3, 4))\n",
    "print(arr)\n",
    "print(\"----------------------------------------------------\")\n",
    "print(arr[0])\n",
    "print(\"----------------------------------------------------\")\n",
    "print(arr[:, 0])\n",
    "print(\"----------------------------------------------------\")\n",
    "print(arr.shape)\n",
    "print(\"----------------------------------------------------\")\n",
    "arr = np.expand_dims(arr, 0)\n",
    "print(arr.shape)\n",
    "print(\"----------------------------------------------------\")\n",
    "print(arr[0])\n",
    "print(\"----------------------------------------------------\")\n",
    "print(arr[:, 0])\n",
    "print(\"----------------------------------------------------\")\n",
    "print(arr)\n",
    "print(\"----------------------------------------------------\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 通用函数\n",
    "到目前为止，我们讨论了 NumPy 的一些基础知识。在接下来的几小节中，我们将深入了解 NumPy 在 Python 数据科学世界中如此重要的原因。明确点说，NumPy 提供了一个简单灵活的接口来优化数据数组的计算。\n",
    "\n",
    "NumPy 数组的计算有时非常快，有时也非常慢。使 NumPy 变快的关键是利用向量化操作，通常在 NumPy 的通用函数（ufunc）中实现。本节将介绍 NumPy 通用函数的重要性——它可以提高数组元素的重复计算的效率；然后，将会介绍很多 NumPy 包中常用且有用的数学通用函数。\n",
    "\n",
    "我们再回顾下之前求倒数的例子："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 125,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0.16666667, 1.        , 0.25      , 0.25      , 0.125     ])"
      ]
     },
     "execution_count": 125,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import numpy as np\n",
    "np.random.seed(0)\n",
    "\n",
    "def compute_reciprocals(values):\n",
    "    output = np.empty(len(values))\n",
    "    for i in range(len(values)):\n",
    "        output[i] = 1.0 / values[i]\n",
    "    return output\n",
    "\n",
    "values = np.random.randint(1, 10, size=5)\n",
    "compute_reciprocals(values)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "NumPy 为很多类型的操作提供了非常方便的、静态类型的、可编译程序的接口，也被称作向量操作。你可以通过简单地对数组执行操作来实现，这里对数组的操作将会被用于数组中的每一个元素。这种向量方法被用于将循环推送至 NumPy 之下的编译层，这样会取得更快的执行效率。\n",
    "\n",
    "比较如下结果："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 126,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0.16666667 1.         0.25       0.25       0.125     ]\n",
      "[0.16666667 1.         0.25       0.25       0.125     ]\n"
     ]
    }
   ],
   "source": [
    "print(compute_reciprocals(values))\n",
    "print(1.0 / values)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 127,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1.86 s ± 49.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n",
      "694 µs ± 174 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)\n"
     ]
    }
   ],
   "source": [
    "big_array = np.random.randint(1, 100, size=1000000)\n",
    "%timeit compute_reciprocals(big_array)\n",
    "%timeit (1.0 / big_array)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 128,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 1.83 s, sys: 11.7 ms, total: 1.85 s\n",
      "Wall time: 1.85 s\n",
      "CPU times: user 812 µs, sys: 145 µs, total: 957 µs\n",
      "Wall time: 676 µs\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "array([0.1       , 0.01190476, 0.04545455, ..., 0.01428571, 0.01098901,\n",
       "       0.01149425])"
      ]
     },
     "execution_count": 128,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "%time compute_reciprocals(big_array)\n",
    "%time (1.0 / big_array)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "NumPy 中的向量操作是通过通用函数实现的。通用函数的主要目的是对 NumPy 数组中的值执行更快的重复操作。它非常灵活，前面我们看过了标量和数组的运算，但是也可以对两个数组进行运算："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 129,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0.        , 0.5       , 0.66666667, 0.75      , 0.8       ])"
      ]
     },
     "execution_count": 129,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.arange(5) / np.arange(1, 6)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "通用函数并不仅限于一维数组的运算，它们也可以进行多维数组的运算："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 130,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[  1,   2,   4],\n",
       "       [  8,  16,  32],\n",
       "       [ 64, 128, 256]])"
      ]
     },
     "execution_count": 130,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x = np.arange(9).reshape((3, 3))\n",
    "2 ** x"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "通过通用函数用向量的方式进行计算几乎总比用 Python 循环实现的计算更加有效，尤其是当数组很大时。只要你看到 Python 脚本中有这样的循环，就应该考虑能否用向量方式替换这个循环。\n",
    "\n",
    "给出一些例子，具体的请查阅相关资料："
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "$$\n",
    "\\begin{array}{lll}\n",
    "\n",
    "\\end{array}\n",
    "$$\n",
    "$$\n",
    "\\begin{equation}\n",
    "\n",
    "\\begin{array}{lll}\n",
    "\\hline \\text { 运算符 } & \\text { 对应的通用函数 } & \\text { 描述 } \\\\\n",
    "\\hline+ & n p . a d d & \\text { 加法运算 }(\\text { 即 } 1+1=2) \\\\\n",
    "- & n p . \\text { subtract } & \\text { 减法运算 }(\\text { 即 } 3-2=1) \\\\\n",
    "- & \\text { np.negative } & \\text { 负数运算 }(\\text { 即 }-2) \\\\\n",
    "\n",
    "* & \\text { np.multiply } & \\text { 乘法运算 }(\\text { 即 } 2 * 3=6) \\\\\n",
    "\n",
    "/ & \\text { np.divide } & \\text { 除法运算 }(\\text { 即 } 3 / 2=1.5) \\\\\n",
    "\n",
    "/ / & \\text { np.floor}\\text {divide } & \\text { 地板除法运算 (floor division，即 } 3 / / 2=1) \\\\\n",
    "\n",
    "\\text { ** } & \\text { np.power } & \\text { 指数运算 }(\\text { 即 } 2 * * 3=8) \\\\\n",
    "\n",
    "\\% & \\text { np.mod } & \\text { 模 / 余数 }(\\text { 即 } 9 \\% 4=1) \\\\\n",
    "\n",
    "\\hline\n",
    "\n",
    "\\end{array}\n",
    "\n",
    "\\end{equation}\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "专用的通用函数\n",
    "除了以上介绍到的，NumPy 还提供了很多通用函数，包括双曲三角函数、比特位运算、比较运算符、弧度转化为角度的运算、取整和求余运算，等等。浏览 NumPy 的文档将会揭示很多有趣的功能。\n",
    "\n",
    "还有一个更加专用，也更加晦涩的通用函数优异来源是子模块 scipy.special。如果你希望对你的数据进行一些更晦涩的数学计算，scipy.special 可能包含了你需要的计算函数。\n",
    "\n",
    "这些函数能列一个长长的列表，下面的代码片段展示了一些可能在统计学中用到的函数："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 131,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "gamma(x) = [1.0000e+00 2.4000e+01 3.6288e+05]\n",
      "ln|gamma(x)| = [ 0.          3.17805383 12.80182748]\n",
      "beta(x, 2) = [0.5        0.03333333 0.00909091]\n",
      "erf(x) = [0.         0.32862676 0.67780119 0.84270079]\n",
      "erfc(x) = [1.         0.67137324 0.32219881 0.15729921]\n",
      "erfinv(x) = [0.         0.27246271 0.73286908        inf]\n"
     ]
    }
   ],
   "source": [
    "from scipy import special\n",
    "# Gamma函数（广义阶乘，generalized factorials）和相关函数\n",
    "x = [1, 5, 10]\n",
    "print(\"gamma(x) =\", special.gamma(x))\n",
    "print(\"ln|gamma(x)| =\", special.gammaln(x))\n",
    "print(\"beta(x, 2) =\", special.beta(x, 2))\n",
    " # 误差函数（高斯积分）\n",
    " # 它的实现和它的逆实现\n",
    "x = np.array([0, 0.3, 0.7, 1.0])\n",
    "print(\"erf(x) =\", special.erf(x))\n",
    "print(\"erfc(x) =\", special.erfc(x))\n",
    "print(\"erfinv(x) =\", special.erfinv(x))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "NumPy 和 scipy.special 中提供了大量的通用函数，这些包的文档在网上就可以查到，搜索“gamma function python”即可。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 高级的通用函数特性\n",
    "\n",
    "1. 指定输出\n",
    "\n",
    "在进行大量运算时，有时候指定一个用于存放运算结果的数组是非常有用的。不同于创建临时数组，你可以用这个特性将计算结果直接写入到你期望的存储位置。所有的通用函数都可以通过 out 参数来指定计算结果的存放位置："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 165,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[ 0. 10. 20. 30. 40.]\n"
     ]
    }
   ],
   "source": [
    "x = np.arange(5)\n",
    "y = np.empty(5)\n",
    "np.multiply(x, 10, out=y) \n",
    "print(y)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这个特性也可以被用作数组视图，例如可以将计算结果写入指定数组的每隔一个元素的位置："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 166,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[ 1.  0.  2.  0.  4.  0.  8.  0. 16.  0.]\n"
     ]
    }
   ],
   "source": [
    "y = np.zeros(10)\n",
    "np.power(2, x, out=y[::2])\n",
    "print(y)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如果这里写的是 y[::2] = 2 ** x，那么结果将是创建一个临时数组，该数组存放的是2 ** x 的结果，并且接下来会将这些值复制到 y 数组中。对于上述例子中比较小的计算量来说，这两种方式的差别并不大。但是对于较大的数组，通过慎重使用 out 参数将能够有效节约内存。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "2.聚合\n",
    "\n",
    "二元通用函数有些非常有趣的聚合功能，这些聚合可以直接在对象上计算。例如，如果我们希望用一个特定的运算 reduce 一个数组，那么可以用任何通用函数的 reduce 方法。一个 reduce 方法会对给定的元素和操作重复执行，直至得到单个的结果。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 167,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "15\n",
      "[ 1  3  6 10 15]\n"
     ]
    }
   ],
   "source": [
    "x = np.arange(1, 6)\n",
    "print(np.add.reduce(x))\n",
    "print(np.add.accumulate(x))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "3. 外积\n",
    "\n",
    "有点类似张亮积。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 168,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 1,  2,  3,  4,  5],\n",
       "       [ 2,  4,  6,  8, 10],\n",
       "       [ 3,  6,  9, 12, 15],\n",
       "       [ 4,  8, 12, 16, 20],\n",
       "       [ 5, 10, 15, 20, 25]])"
      ]
     },
     "execution_count": 168,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x = np.arange(1, 6)\n",
    "np.multiply.outer(x, x)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 广播机制\n",
    "我们在前一节中介绍了 NumPy 如何通过通用函数的向量化操作来减少缓慢的 Python 循环，另外一种向量化操作的方法是利用 NumPy 的广播功能。广播可以简单理解为用于不同大小数组的二进制通用函数（加、减、乘等）的一组规则。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 132,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([5, 6, 7])"
      ]
     },
     "execution_count": 132,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "a = np.array([0, 1, 2])\n",
    "b = np.array([5, 5, 5])\n",
    "a + b"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "还可以自动扩充计算"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 133,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0, 1, 2],\n",
       "       [1, 2, 3],\n",
       "       [2, 3, 4]])"
      ]
     },
     "execution_count": 133,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "a = np.arange(3)\n",
    "b = np.arange(3)[:, np.newaxis]\n",
    "a + b"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 规则！\n",
    "\n",
    "NumPy 的广播遵循一组严格的规则，设定这组规则是为了决定两个数组间的操作。\n",
    "\n",
    "- 规则 1：如果两个数组的维度数不相同，那么小维度数组的形状将会在最左边补 1\n",
    "\n",
    "- 规则 2：如果两个数组的形状在任何一个维度上都不匹配，那么数组的形状会沿着维度为 1 的维度扩展以匹配另外一个数组的形状\n",
    "\n",
    "- 规则 3：如果两个数组的形状在任何一个维度上都不匹配并且没有任何一个维度等于 1，那么会引发异常"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 134,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0, 1, 2],\n",
       "       [1, 2, 3],\n",
       "       [2, 3, 4]])"
      ]
     },
     "execution_count": 134,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "a = np.arange(3).reshape((3, 1))\n",
    "b = np.arange(3)\n",
    "a + b"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 135,
   "metadata": {},
   "outputs": [
    {
     "ename": "ValueError",
     "evalue": "operands could not be broadcast together with shapes (3,2) (3,) ",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mValueError\u001b[0m                                Traceback (most recent call last)",
      "\u001b[1;32m/Users/zhaochen20/Desktop/Python 科学计算.ipynb Cell 126\u001b[0m in \u001b[0;36m<cell line: 3>\u001b[0;34m()\u001b[0m\n\u001b[1;32m      <a href='vscode-notebook-cell:/Users/zhaochen20/Desktop/Python%20%E7%A7%91%E5%AD%A6%E8%AE%A1%E7%AE%97.ipynb#ch0000140?line=0'>1</a>\u001b[0m M \u001b[39m=\u001b[39m np\u001b[39m.\u001b[39mones((\u001b[39m3\u001b[39m, \u001b[39m2\u001b[39m))\n\u001b[1;32m      <a href='vscode-notebook-cell:/Users/zhaochen20/Desktop/Python%20%E7%A7%91%E5%AD%A6%E8%AE%A1%E7%AE%97.ipynb#ch0000140?line=1'>2</a>\u001b[0m a \u001b[39m=\u001b[39m np\u001b[39m.\u001b[39marange(\u001b[39m3\u001b[39m)\n\u001b[0;32m----> <a href='vscode-notebook-cell:/Users/zhaochen20/Desktop/Python%20%E7%A7%91%E5%AD%A6%E8%AE%A1%E7%AE%97.ipynb#ch0000140?line=2'>3</a>\u001b[0m M \u001b[39m+\u001b[39;49m a\n",
      "\u001b[0;31mValueError\u001b[0m: operands could not be broadcast together with shapes (3,2) (3,) "
     ]
    }
   ],
   "source": [
    "M = np.ones((3, 2))\n",
    "a = np.arange(3)\n",
    "M + a"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 广播的实际运用\n",
    "\n",
    "1. 数组的归一化"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 140,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([-9.99200722e-17,  3.33066907e-17,  0.00000000e+00])"
      ]
     },
     "execution_count": 140,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "X = np.random.random((10, 3))\n",
    "Xmean = X.mean(axis=0)\n",
    "X_centered = X - Xmean\n",
    "X_centered.mean(axis=0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在机器精度范围内，该均值为 0。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "2. 绘制二维函数\n",
    "\n",
    "广播另外一个非常有用的地方在于，它能基于二维函数显示图像。比如我们希望定义一个函数 $z = f (x, y)$，可以用广播沿着数值区间计算该函数："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 141,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 432x288 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "x = np.linspace(0, 5, 50)\n",
    "y = np.linspace(0, 5, 50)[:, np.newaxis]\n",
    "z = np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x)\n",
    "plt.imshow(z, extent=[0, 5, 0, 5], cmap='viridis')\n",
    "plt.colorbar()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 反序\n",
    "\n",
    "⭐\n",
    "\n",
    "也可以看作是一种对原数组的转换，用的不多，可以了解一下，为接下来的索引和切片做个热身。\n",
    "\n",
    "如果给一个字符串或数组让您反序，您可能会想到很多种方法，比如：`reversed`，或者写一个方法，或者用 Python list 的索引功能，而这也是 `numpy` 中 array 反序的方式。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 142,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'loveu'"
      ]
     },
     "execution_count": 142,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 字符串\n",
    "s = \"uevol\"\n",
    "s[::-1]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 143,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[5.2, '1', 1]"
      ]
     },
     "execution_count": 143,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 数组\n",
    "lst = [1, \"1\", 5.2]\n",
    "lst[::-1]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 160,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[49 81 30 11]\n",
      " [54 17 22 45]\n",
      " [34  5 40  9]]\n"
     ]
    }
   ],
   "source": [
    "arr = np.random.randint(1, 100, (3, 4))\n",
    "print(arr)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 161,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[34,  5, 40,  9],\n",
       "       [54, 17, 22, 45],\n",
       "       [49, 81, 30, 11]])"
      ]
     },
     "execution_count": 161,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 我们按上面的套路：默认列反序\n",
    "arr[::-1]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 162,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[34,  5, 40,  9],\n",
       "       [54, 17, 22, 45],\n",
       "       [49, 81, 30, 11]])"
      ]
     },
     "execution_count": 162,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 列不变行反序\n",
    "arr[::-1, :]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 163,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[11, 30, 81, 49],\n",
       "       [45, 22, 17, 54],\n",
       "       [ 9, 40,  5, 34]])"
      ]
     },
     "execution_count": 163,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 在不同维度上操作：行不变列反序\n",
    "arr[:, ::-1]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 164,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 9, 40,  5, 34],\n",
       "       [45, 22, 17, 54],\n",
       "       [11, 30, 81, 49]])"
      ]
     },
     "execution_count": 164,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 行变列也变\n",
    "arr[::-1, ::-1]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 转置\n",
    "\n",
    "⭐⭐⭐\n",
    "\n",
    "转置是线性代数的基本操作，拿二维矩阵为例，通俗理解就是把它放倒，shape 反转，行变成列，列成为行。当然，对于多维也是类似的，需要里理解他们的数学意义，这里不做展开。我们建议您二维矩阵用 `arr.T`（会快很多），超过二维的张量可以用 `np.transpose`，会更加灵活些。\n",
    "\n",
    "⚠️ 需要注意的是：一维数组转置还是自己。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 169,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[1 2 3]]\n",
      "(1, 3)\n",
      "[[1]\n",
      " [2]\n",
      " [3]]\n",
      "(3, 1)\n"
     ]
    }
   ],
   "source": [
    "# 一维\n",
    "print(\"----------------------------------------------------\")\n",
    "print(np.array([[1, 2, 3]]))\n",
    "print(\"----------------------------------------------------\")\n",
    "print(np.array([[1, 2, 3]]).shape)\n",
    "print(\"----------------------------------------------------\")\n",
    "print(np.array([[1, 2, 3]]).T)\n",
    "print(\"----------------------------------------------------\")\n",
    "print(np.array([[1,2, 3]]).T.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 171,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "----------------------------------------------------\n",
      "[[ 9 77 65 44]\n",
      " [43 86  9 70]\n",
      " [20 10 53 97]]\n",
      "----------------------------------------------------\n",
      "(3, 4)\n",
      "----------------------------------------------------\n",
      "[[ 9 43 20]\n",
      " [77 86 10]\n",
      " [65  9 53]\n",
      " [44 70 97]]\n",
      "----------------------------------------------------\n",
      "(4, 3)\n"
     ]
    }
   ],
   "source": [
    "# 简便用法，把所有维度顺序都给倒过来\n",
    "rng = np.random.default_rng(seed=42)\n",
    "arr = rng.integers(1, 100, (3, 4))\n",
    "print(\"----------------------------------------------------\")\n",
    "print(arr)\n",
    "print(\"----------------------------------------------------\")\n",
    "print(arr.shape)\n",
    "print(\"----------------------------------------------------\")\n",
    "print(arr.T)\n",
    "print(\"----------------------------------------------------\")\n",
    "print(arr.T.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 172,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(4, 3, 1, 1)"
      ]
     },
     "execution_count": 172,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 将 shape=(1,1,3,4) 的转置后得到 shape=(4,3,1,1)\n",
    "arr.reshape(1, 1, 3, 4).T.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 173,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(1, 3, 1, 2, 2, 1)"
      ]
     },
     "execution_count": 173,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 同上\n",
    "arr.reshape(1, 2, 2, 1, 3, 1).T.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### `np.transpose`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 174,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 9, 43, 20],\n",
       "       [77, 86, 10],\n",
       "       [65,  9, 53],\n",
       "       [44, 70, 97]])"
      ]
     },
     "execution_count": 174,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 这种转置方式可以指定 axes\n",
    "np.transpose(arr)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 175,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(1, 3, 1, 2, 2, 1)"
      ]
     },
     "execution_count": 175,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 不指定 axes 时和 .T 是一样的\n",
    "np.transpose(arr.reshape(1, 2, 2, 1, 3, 1)).shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 181,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "----------------------------------------------------\n",
      "[[[[ 9 77 65 44]\n",
      "   [43 86  9 70]\n",
      "   [20 10 53 97]]]]\n",
      "----------------------------------------------------\n",
      "(1, 1, 3, 4)\n",
      "----------------------------------------------------\n",
      "[[[[ 9 77 65 44]]\n",
      "\n",
      "  [[43 86  9 70]]\n",
      "\n",
      "  [[20 10 53 97]]]]\n",
      "----------------------------------------------------\n",
      "(1, 3, 1, 4)\n",
      "----------------------------------------------------\n"
     ]
    }
   ],
   "source": [
    "# 指定 axes，不过 axes 数量必须包含所有维度的序列\n",
    "# 比如两个维度就是 (0, 1)，四个就是 (0, 1, 2, 3)\n",
    "# 当然，顺序可以改变，比如下面就是只转置第 2 个和第 3 个维度\n",
    "# 注意，只有超过 2 维时，这样才有意义\n",
    "# 下面的结果中，中间2个维度被调换顺序了，顺序就在axes中指定的\n",
    "print(\"----------------------------------------------------\")\n",
    "print(arr.reshape(1, 1, 3, 4))\n",
    "print(\"----------------------------------------------------\")\n",
    "print(arr.reshape(1, 1, 3, 4).shape)\n",
    "print(\"----------------------------------------------------\")\n",
    "print(np.transpose(arr.reshape(1, 1, 3, 4), (0, 2, 1, 3)))\n",
    "print(\"----------------------------------------------------\")\n",
    "print(np.transpose(arr.reshape(1, 1, 3, 4), axes=(0, 2, 1, 3)).shape)\n",
    "print(\"----------------------------------------------------\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 分解和组合\n",
    "\n",
    "这节我们主要学习 array 的分解和组合，本节是所有章节中最重要的一节，通过本节内容，您可以充分了解 `numpy`（以及 Python 语言）的强大，这种操作上的优雅不能说后无来者，但至少前无古人了。\n",
    "\n",
    "内容大致包括以下小节：\n",
    "\n",
    "- 切片和索引\n",
    "- 拼接\n",
    "- 重复\n",
    "- 分拆\n",
    "\n",
    "其中，重中之重是「切片和索引」，它基础、它高频、它无处不在。我们强烈建议您熟练掌握，其他三个相对简单，只需要各记住一个 API 即可。\n",
    "\n",
    "### 切片和索引\n",
    "\n",
    "⭐⭐⭐⭐⭐\n",
    "\n",
    "划重点！切片和索引是通过对已有 array 进行操作而得到想要的「部分」元素的行为过程。其核心动作可以概括为：按维度根据 `start:stop:step` 操作 array。\n",
    "\n",
    "这部分内容的核心是把处理按维度分开，不处理的维度统一用 `:` 或 `...` 代替；在看操作时，也要首先关注「`,`」在哪里。要处理的维度和之前 `arange` `linspace` 等接口使用方法是一样的。\n",
    "\n",
    "⚠️ 需要注意的是：引支持负数，即从后往前索引。\n",
    "\n",
    "传统的索引前面其实有讲过不少，可以自行参考 list 的切片和索引。在传统的索引上，我需要重点强调下这个话题：\n",
    "\n",
    "> 关于数组切片有一点很重要非常有用，那就是数组切片返回的是数组数据的视图(也可以理解成引用)，而不是数值数据的副本。这一点也是 NumPy 数组切片和 Python 列表切片的不同之处。在 Python 列表中，切片是值的副本。具体可以查看下方例子："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 182,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "----------------------------------------------------\n",
      "[[0 7 6 4]\n",
      " [4 8 0 6]\n",
      " [2 0 5 9]]\n",
      "----------------------------------------------------\n",
      "__slice__\n",
      "----------------------------------------------------\n",
      "[0 7 6 4]\n",
      "----------------------------------------------------\n",
      "[0 4 2]\n",
      "----------------------------------------------------\n",
      "__change the line__\n",
      "----------------------------------------------------\n",
      "[[1 7 6 4]\n",
      " [4 8 0 6]\n",
      " [2 0 5 9]]\n",
      "----------------------------------------------------\n",
      "__change the column__\n",
      "----------------------------------------------------\n",
      "[[ 1  7  6  4]\n",
      " [ 4  8  0  6]\n",
      " [10  0  5  9]]\n",
      "----------------------------------------------------\n"
     ]
    }
   ],
   "source": [
    "rng = np.random.default_rng(seed=42)\n",
    "x2 = rng.integers(10, size=(3, 4))\n",
    "print(\"----------------------------------------------------\")\n",
    "print(x2)\n",
    "x3 = x2[0]\n",
    "x4 = x2[:, 0]\n",
    "print(\"----------------------------------------------------\")\n",
    "print(\"__slice__\")\n",
    "print(\"----------------------------------------------------\")\n",
    "print(x3)\n",
    "print(\"----------------------------------------------------\")\n",
    "print(x4)\n",
    "x3[0] = 1\n",
    "print(\"----------------------------------------------------\")\n",
    "print(\"__change the line__\")\n",
    "print(\"----------------------------------------------------\")\n",
    "print(x2)\n",
    "print(\"----------------------------------------------------\")\n",
    "x4[2] = 10\n",
    "print(\"__change the column__\")\n",
    "print(\"----------------------------------------------------\")\n",
    "print(x2)\n",
    "print(\"----------------------------------------------------\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 183,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "----------------------------------------------------\n",
      "['c7w', 'lambda', 'Alan']\n",
      "----------------------------------------------------\n",
      "['c7w', 'lambda', 'Eren']\n",
      "----------------------------------------------------\n",
      "['c7w', 'lambda', 'Alan', 'xsun2001', 'ayf']\n",
      "----------------------------------------------------\n"
     ]
    }
   ],
   "source": [
    "lecturer_list = [\"c7w\", \"lambda\", \"Alan\", \"xsun2001\", \"ayf\"]\n",
    "bachelordom_list = lecturer_list[:-2]\n",
    "print(\"----------------------------------------------------\")\n",
    "print(bachelordom_list)\n",
    "print(\"----------------------------------------------------\")\n",
    "bachelordom_list[-1] = \"Eren\"\n",
    "print(bachelordom_list)\n",
    "print(\"----------------------------------------------------\")\n",
    "print(lecturer_list)\n",
    "print(\"----------------------------------------------------\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Fancy Indexing\n",
    "\n",
    "你应该知道如何利用简单的索引值（如 arr[0]）、切片（如 arr[:5]）获得并修改部分数组。在这一节中，我们将介绍另外一种数组索引，也称作花哨的索引（fancy indexing）。花哨的索引和前面那些简单的索引非常类似，但是传递的是索引数组，而不是单个标量。花哨的索引让我们能够快速获得并修改复杂的数组值的子数据集。\n",
    "\n",
    "#### 操作布尔数组\n",
    "\n",
    "之前介绍了通用函数，并且特别关注了算术运算符。我们看到用 +、-、*、/ 和其他一些运算符实现了数组的逐元素操作。NumPy 还实现了如 <（小于）和 >（大于）的逐元素比较的通用函数。这些比较运算的结果是一个布尔数据类型的数组。一共有 6 种标准的比较操作："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 236,
   "metadata": {},
   "outputs": [],
   "source": [
    "x = np.array([1, 2, 3, 4, 5])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 237,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([ True,  True, False, False, False])"
      ]
     },
     "execution_count": 237,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x < 3  # less than"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 238,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([False, False, False,  True,  True])"
      ]
     },
     "execution_count": 238,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x > 3  # greater than"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 239,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([ True,  True,  True, False, False])"
      ]
     },
     "execution_count": 239,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x <= 3  # less than or equal"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 240,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([False, False,  True,  True,  True])"
      ]
     },
     "execution_count": 240,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x >= 3  # greater than or equal"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 241,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([ True,  True, False,  True,  True])"
      ]
     },
     "execution_count": 241,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x != 3  # not equal"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 242,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([False, False,  True, False, False])"
      ]
     },
     "execution_count": 242,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x == 3  # equal"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "和算术运算通用函数一样，这些比较运算通用函数也可以用于任意形状、大小的数组。给定一个布尔数组，你可以实现很多有用的操作。首先打印出二维数组 x："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 244,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[5, 0, 3, 3],\n",
       "       [7, 9, 3, 5],\n",
       "       [2, 4, 7, 6]])"
      ]
     },
     "execution_count": 244,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "rng = np.random.RandomState(0)\n",
    "x = rng.randint(10, size=(3, 4))\n",
    "x"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "你当然可以直接对布尔数组进行聚合计算，这里不再赘述，然而我们讨论更强大的工具——\n",
    "\n",
    "用布尔数组作为掩码，通过该掩码选择数据的子数据集。以前面小节用过的 x 数组为例，假设我们希望抽取出数组中所有小于 5 的元素："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 245,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0, 3, 3, 3, 2, 4])"
      ]
     },
     "execution_count": 245,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x[x < 5]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "现在返回的是一个一维数组，它包含了所有满足条件的值。换句话说，所有的这些值是掩码数组对应位置为 True 的值。\n",
    "\n",
    "通过将布尔操作、掩码操作和聚合结合，可以快速查看数据集内的数据属性。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 探索花哨的索引\n",
    "\n",
    "花哨的索引在概念上非常简单，它意味着传递一个索引数组来一次性获得多个数组元素。\n",
    "\n",
    "例如以下数组："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 246,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[51 92 14 71 60 20 82 86 74 74]\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "rand = np.random.RandomState(42)\n",
    "\n",
    "x = rand.randint(100, size=10)\n",
    "print(x)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "假设我们希望获得三个不同的元素，可以用以下方式实现："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 247,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[71, 86, 14]"
      ]
     },
     "execution_count": 247,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "[x[3], x[7], x[2]]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "利用花哨的索引，结果的形状与索引数组的形状一致，而不是与被索引数组的形状一致："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 249,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[71, 86],\n",
       "       [60, 20]])"
      ]
     },
     "execution_count": 249,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ind = np.array([[3, 7],\n",
    "                [4, 5]])\n",
    "x[ind]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "花哨的索引也对多个维度适用。假设我们有以下数组："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 250,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 0,  1,  2,  3],\n",
       "       [ 4,  5,  6,  7],\n",
       "       [ 8,  9, 10, 11]])"
      ]
     },
     "execution_count": 250,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "X = np.arange(12).reshape((3, 4))\n",
    "X"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "和标准的索引方式一样，第一个索引指的是行，第二个索引指的是列："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 251,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([ 2,  5, 11])"
      ]
     },
     "execution_count": 251,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "row = np.array([0, 1, 2])\n",
    "col = np.array([2, 1, 3])\n",
    "X[row, col]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "另外一种方法是通过传递索引的单个列表或数组来获得同样的结果："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 248,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([71, 86, 60])"
      ]
     },
     "execution_count": 248,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ind = [3, 7, 4]\n",
    "x[ind]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里需要注意，结果的第一个值是 X[0, 2]，第二个值是 X[1, 1]，第三个值是 X[2, 3]在花哨的索引中，索引值的配对遵循介绍过的广播的规则。因此当我们将一个列向量和一个行向量组合在一个索引中时，会得到一个二维的结果："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 252,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 2,  1,  3],\n",
       "       [ 6,  5,  7],\n",
       "       [10,  9, 11]])"
      ]
     },
     "execution_count": 252,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "X[row[:, np.newaxis], col]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里，每一行的值都与每一列的向量配对，正如我们看到的广播的算术运算："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 253,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0, 0, 0],\n",
       "       [2, 1, 3],\n",
       "       [4, 2, 6]])"
      ]
     },
     "execution_count": 253,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "row[:, np.newaxis] * col"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里特别需要记住的是，花哨的索引返回的值反映的是广播后的索引数组的形状，而不是被索引的数组的形状。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 组合索引\n",
    "\n",
    "花哨的索引可以和其他索引方案结合起来形成更强大的索引操作："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 254,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[ 0  1  2  3]\n",
      " [ 4  5  6  7]\n",
      " [ 8  9 10 11]]\n"
     ]
    }
   ],
   "source": [
    "print(X)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "可以将花哨的索引和简单的索引组合使用："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 255,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([10,  8,  9])"
      ]
     },
     "execution_count": 255,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "X[2, [2, 0, 1]]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "也可以将花哨的索引和切片组合使用："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 256,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 6,  4,  5],\n",
       "       [10,  8,  9]])"
      ]
     },
     "execution_count": 256,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "X[1:, [2, 0, 1]]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "更可以将花哨的索引和掩码组合使用："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 257,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 0,  2],\n",
       "       [ 4,  6],\n",
       "       [ 8, 10]])"
      ]
     },
     "execution_count": 257,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "mask = np.array([1, 0, 1, 0], dtype=bool)\n",
    "X[row[:, np.newaxis], mask]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "索引选项的组合可以实现非常灵活的获取和修改数组元素的操作"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 拼接\n",
    "\n",
    "⭐⭐⭐⭐\n",
    "\n",
    "有时候我们需要对已有的几个 array 进行拼接以形成一个大的 array（常见的例子比如不同类型特征的拼接）。本小节严格来说只有两个 API：`np.concatenate` 和 `np.stack`，前者是拼接，后者是堆叠（会增加一个维度），都可以指定维度。记住，有它俩就够了。\n",
    "\n",
    "⚠️ 需要注意的是：`hstack` 和 `vstack` 和 `stack` 没关系。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 184,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "----------------------------------------------------\n",
      "[[0.77395605 0.43887844 0.85859792]\n",
      " [0.69736803 0.09417735 0.97562235]]\n",
      "----------------------------------------------------\n",
      "[[0.7611397  0.78606431 0.12811363]\n",
      " [0.45038594 0.37079802 0.92676499]]\n",
      "----------------------------------------------------\n",
      "(2, 3)\n",
      "----------------------------------------------------\n",
      "(2, 3)\n",
      "----------------------------------------------------\n"
     ]
    }
   ],
   "source": [
    "rng = np.random.default_rng(42)\n",
    "\n",
    "arr1 = rng.random((2, 3))\n",
    "arr2 = rng.random((2, 3))\n",
    "print(\"----------------------------------------------------\")\n",
    "print(arr1)\n",
    "print(\"----------------------------------------------------\")\n",
    "print(arr2)\n",
    "print(\"----------------------------------------------------\")\n",
    "print(arr1.shape)\n",
    "print(\"----------------------------------------------------\")\n",
    "print(arr2.shape)\n",
    "print(\"----------------------------------------------------\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 185,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "----------------------------------------------------\n",
      "[[0.77395605 0.43887844 0.85859792]\n",
      " [0.69736803 0.09417735 0.97562235]\n",
      " [0.7611397  0.78606431 0.12811363]\n",
      " [0.45038594 0.37079802 0.92676499]]\n",
      "----------------------------------------------------\n",
      "(4, 3)\n",
      "----------------------------------------------------\n",
      "[[0.77395605 0.43887844 0.85859792 0.7611397  0.78606431 0.12811363]\n",
      " [0.69736803 0.09417735 0.97562235 0.45038594 0.37079802 0.92676499]]\n",
      "----------------------------------------------------\n",
      "(2, 6)\n",
      "----------------------------------------------------\n"
     ]
    }
   ],
   "source": [
    "# 默认沿axis=0（列）连接\n",
    "print(\"----------------------------------------------------\")\n",
    "print(np.concatenate((arr1, arr2)))\n",
    "print(\"----------------------------------------------------\")\n",
    "print(np.concatenate((arr1, arr2)).shape)\n",
    "print(\"----------------------------------------------------\")\n",
    "print(np.concatenate((arr1, arr2), axis=1))\n",
    "print(\"----------------------------------------------------\")\n",
    "print(np.concatenate((arr1, arr2), axis=1).shape)\n",
    "print(\"----------------------------------------------------\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 188,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "----------------------------------------------------\n",
      "(2, 6)\n",
      "----------------------------------------------------\n",
      "(4, 3)\n",
      "----------------------------------------------------\n"
     ]
    }
   ],
   "source": [
    "# 竖直按行顺序拼接\n",
    "# 注意：vstack 虽然看起来是 stack，但其实它是 concatenate，建议您只使用 np.concatenate\n",
    "print(\"----------------------------------------------------\")\n",
    "print(np.hstack((arr1, arr2)).shape)\n",
    "print(\"----------------------------------------------------\")\n",
    "print(np.vstack((arr1, arr2)).shape)\n",
    "print(\"----------------------------------------------------\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 189,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[[0.77395605, 0.43887844, 0.85859792],\n",
       "        [0.69736803, 0.09417735, 0.97562235]],\n",
       "\n",
       "       [[0.7611397 , 0.78606431, 0.12811363],\n",
       "        [0.45038594, 0.37079802, 0.92676499]]])"
      ]
     },
     "execution_count": 189,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 堆叠，默认根据 axis=0 进行\n",
    "np.stack((arr1, arr2))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 190,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(2, 2, 3)"
      ]
     },
     "execution_count": 190,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "_.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 191,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "----------------------------------------------------\n",
      "[[[0.77395605 0.43887844 0.85859792]\n",
      "  [0.7611397  0.78606431 0.12811363]]\n",
      "\n",
      " [[0.69736803 0.09417735 0.97562235]\n",
      "  [0.45038594 0.37079802 0.92676499]]]\n",
      "----------------------------------------------------\n",
      "(2, 2, 3)\n",
      "----------------------------------------------------\n",
      "[[[0.77395605 0.7611397 ]\n",
      "  [0.43887844 0.78606431]\n",
      "  [0.85859792 0.12811363]]\n",
      "\n",
      " [[0.69736803 0.45038594]\n",
      "  [0.09417735 0.37079802]\n",
      "  [0.97562235 0.92676499]]]\n",
      "----------------------------------------------------\n",
      "(2, 3, 2)\n",
      "----------------------------------------------------\n"
     ]
    }
   ],
   "source": [
    "# 堆叠，根据 axis=2\n",
    "print(\"----------------------------------------------------\")\n",
    "print(np.stack((arr1, arr2), axis=1))\n",
    "print(\"----------------------------------------------------\")\n",
    "print(np.stack((arr1, arr2), axis=1).shape)\n",
    "print(\"----------------------------------------------------\")\n",
    "print(np.stack((arr1, arr2), axis=2))\n",
    "print(\"----------------------------------------------------\")\n",
    "print(np.stack((arr1, arr2), axis=2).shape)\n",
    "print(\"----------------------------------------------------\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 重复\n",
    "\n",
    "⭐⭐⭐\n",
    "\n",
    "重复其实是另一种拼接方式，它也可以指定要重复的维度。在有些深度学习模型数据构建中非常有用（方便）。\n",
    "\n",
    "⚠️ 需要注意的是：是一个维度一个维度依次重复，而不是整个 array 重复。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 192,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0, 7, 6, 4],\n",
       "       [4, 8, 0, 6],\n",
       "       [2, 0, 5, 9]])"
      ]
     },
     "execution_count": 192,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "rng = np.random.default_rng(42)\n",
    "arr = rng.integers(0, 10, (3, 4))\n",
    "arr"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 193,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0, 7, 6, 4],\n",
       "       [0, 7, 6, 4],\n",
       "       [4, 8, 0, 6],\n",
       "       [4, 8, 0, 6],\n",
       "       [2, 0, 5, 9],\n",
       "       [2, 0, 5, 9]])"
      ]
     },
     "execution_count": 193,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 在 axis=0（沿着列）上重复 2 次\n",
    "np.repeat(arr, 2, axis=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 194,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0, 0, 0, 7, 7, 7, 6, 6, 6, 4, 4, 4],\n",
       "       [4, 4, 4, 8, 8, 8, 0, 0, 0, 6, 6, 6],\n",
       "       [2, 2, 2, 0, 0, 0, 5, 5, 5, 9, 9, 9]])"
      ]
     },
     "execution_count": 194,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 在 axis=1（沿着行）上重复 3 次\n",
    "np.repeat(arr, 3, axis=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 分拆\n",
    "\n",
    "⭐⭐⭐\n",
    "\n",
    "有拼接堆叠自然就有拆分，注意这不是切片和索引，就是将 array 拆成想要的几份。用的不是特别多，API 只要记住 `np.split` 就行了，其他的都是快捷方式。\n",
    "\n",
    "⚠️ 需要注意的是：分拆的 axis 是对该维度进行拆分。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 195,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 9, 77, 65, 44],\n",
       "       [43, 86,  9, 70],\n",
       "       [20, 10, 53, 97],\n",
       "       [73, 76, 72, 78],\n",
       "       [51, 13, 84, 45],\n",
       "       [50, 37, 19, 92]])"
      ]
     },
     "execution_count": 195,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "rng = np.random.default_rng(42)\n",
    "arr = rng.integers(1, 100, (6, 4))\n",
    "arr"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### `np.split`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 196,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[array([[ 9, 77, 65, 44],\n",
       "        [43, 86,  9, 70]]),\n",
       " array([[20, 10, 53, 97],\n",
       "        [73, 76, 72, 78]]),\n",
       " array([[51, 13, 84, 45],\n",
       "        [50, 37, 19, 92]])]"
      ]
     },
     "execution_count": 196,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 默认切分列（axis=0），切成 3 份\n",
    "np.split(arr, 3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 197,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[array([[ 9, 77],\n",
       "        [43, 86],\n",
       "        [20, 10],\n",
       "        [73, 76],\n",
       "        [51, 13],\n",
       "        [50, 37]]),\n",
       " array([[65, 44],\n",
       "        [ 9, 70],\n",
       "        [53, 97],\n",
       "        [72, 78],\n",
       "        [84, 45],\n",
       "        [19, 92]])]"
      ]
     },
     "execution_count": 197,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# （axis=1）切分行\n",
    "np.split(arr, 2, axis=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 198,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[array([[ 9, 77, 65, 44],\n",
       "        [43, 86,  9, 70]]),\n",
       " array([[20, 10, 53, 97],\n",
       "        [73, 76, 72, 78]]),\n",
       " array([[51, 13, 84, 45],\n",
       "        [50, 37, 19, 92]])]"
      ]
     },
     "execution_count": 198,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 和上面的一个效果\n",
    "np.vsplit(arr, 3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 200,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[array([[ 9, 77],\n",
       "        [43, 86],\n",
       "        [20, 10],\n",
       "        [73, 76],\n",
       "        [51, 13],\n",
       "        [50, 37]]),\n",
       " array([[65, 44],\n",
       "        [ 9, 70],\n",
       "        [53, 97],\n",
       "        [72, 78],\n",
       "        [84, 45],\n",
       "        [19, 92]])]"
      ]
     },
     "execution_count": 200,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 等价的用法\n",
    "np.hsplit(arr, 2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 筛选和过滤\n",
    "\n",
    "这小节与索引和切片有点类似，但倾向于从「整体」中统一筛选出「符合条件」的内容，而索引和切片更多的是依照「某种方法」切出一块内容。本小节内容同样非常重要，可以算第二个最重要的小节。主要包括以下内容：\n",
    "\n",
    "- 条件筛选\n",
    "- 提取（按条件）\n",
    "- 抽样（按分布）\n",
    "- 最大最小 index（特殊值）\n",
    "\n",
    "这几个内容都很重要，使用的也非常高频。条件筛选经常用于 Mask 或异常值处理，提取则常用于结果过滤，抽样常用在数据生成（比如负样本抽样），最大最小 index 则常见于机器学习模型预测结果判定中（根据最大概率所在的 index 决定结果属于哪一类）。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 201,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 9, 77, 65, 44],\n",
       "       [43, 86,  9, 70],\n",
       "       [20, 10, 53, 97]])"
      ]
     },
     "execution_count": 201,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "rng = np.random.default_rng(42)\n",
    "arr = rng.integers(1, 100, (3, 4))\n",
    "arr"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 条件筛选\n",
    "\n",
    "⭐⭐⭐\n",
    "\n",
    "顾名思义，根据一定的条件对 array 进行筛选（标记）并后续处理。核心 API 是 `np.where`。\n",
    "\n",
    "⚠️ 需要注意的是：where 分别返回各维度的 index，赋值的是「不满足」条件的。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 203,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[False,  True,  True, False],\n",
       "       [False,  True, False,  True],\n",
       "       [False, False,  True,  True]])"
      ]
     },
     "execution_count": 203,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 条件筛选，可以直接在整个 array 上使用条件\n",
    "arr > 50"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 204,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(array([0, 0, 1, 1, 2, 2]), array([1, 2, 1, 3, 2, 3]))"
      ]
     },
     "execution_count": 204,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 返回满足条件的索引，因为是两个维度，所以会返回两组结果\n",
    "np.where(arr > 50)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 205,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[-1, 77, 65, -1],\n",
       "       [-1, 86, -1, 70],\n",
       "       [-1, -1, 53, 97]])"
      ]
     },
     "execution_count": 205,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 不满足条件的赋值，将 <=50 的替换为 -1\n",
    "np.where(arr > 50, arr, -1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 提取\n",
    "\n",
    "⭐\n",
    "\n",
    "在 array 中提取指定条件的值。\n",
    "\n",
    "⚠️ 需要注意的是：提取和唯一值返回的都是一维向量。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 206,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([77, 65, 86, 70, 53, 97])"
      ]
     },
     "execution_count": 206,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 提取指定条件的值\n",
    "np.extract(arr > 50, arr)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 207,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([ 9, 10, 20, 43, 44, 53, 65, 70, 77, 86, 97])"
      ]
     },
     "execution_count": 207,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 唯一值，是另一种形式的提取\n",
    "np.unique(arr)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 抽样\n",
    "\n",
    "⭐⭐⭐⭐⭐\n",
    "\n",
    "我们在跑模型时常常需要使用部分数据对整个过程快速验证，您当然可以使用 `np.random` 生成模拟数据。但有真实数据时，从真实数据中随机抽样会比较好。\n",
    "\n",
    "⚠️ 需要注意的是：抽样的集合要求为一维向量（一般是数据的 index）。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 208,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array(['vida', 'vivras', 'la', 'sola'], dtype='<U6')"
      ]
     },
     "execution_count": 208,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "rng = np.random.default_rng(42)\n",
    "# 第一个参数是要抽样的集合，如果是一个整数，则表示从 0 到该值\n",
    "# 第二个参数是样本大小\n",
    "# 第三个参数表示结果是否可以重复\n",
    "# 第四个参数表示出现的概率，长度和第一个参数一样\n",
    "\n",
    "# 由于（0 1 2 3）中 2 和 3 的概率比较高，自然就选择了 2 和 3\n",
    "rng.choice(4, 2, replace=False, p=[0.1, 0.2, 0.3, 0.4])\n",
    "lst = [\"la\", \"vida\", \"sola\", \"vivras\"]\n",
    "rng.choice(lst, 4, replace=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 209,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([3711, 5502, 4383, 8491, 8931, 3725, 5223, 2632, 4078, 3705, 5488,\n",
       "       9326, 9338, 3762, 6538, 6579, 3238,   61, 7129, 7451, 6321, 3881,\n",
       "       8517, 6892, 2657, 4858, 6629, 9335, 3288, 2620,   90, 8999, 4165,\n",
       "       7333, 8764, 9840, 4517, 8409, 1938, 3730, 2372, 8088, 7804, 9017,\n",
       "       6956, 6413,  147, 5721, 6089, 8900])"
      ]
     },
     "execution_count": 209,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 旧的 API\n",
    "# 如果是抽样语料的 index，更多的方法是这样：\n",
    "data_size = 10000\n",
    "np.random.choice(data_size, 50, replace=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 最值 Index\n",
    "\n",
    "⭐⭐⭐⭐⭐\n",
    "\n",
    "这小节主要是两个 API：`np.argmax(min)` 和 `np.argsort`，当然最常用的还是第一个，不用说，自然是可以（需要）指定 axis 的。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 210,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[77.62164881, 44.44896554, 86.00119407, 70.03943488],\n",
       "       [10.32355744, 97.58661281, 76.3528305 , 78.82036622],\n",
       "       [13.68324963, 45.58820785, 37.7090044 , 92.7497339 ]])"
      ]
     },
     "execution_count": 210,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "rng = np.random.default_rng(42)\n",
    "arr = rng.uniform(1, 100, (3, 4))\n",
    "arr"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### `np.argmax/argmin`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 211,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "5"
      ]
     },
     "execution_count": 211,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 所有值中最大值的 Index，基本不这么用\n",
    "np.argmax(arr)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 212,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0, 1, 0, 2])"
      ]
     },
     "execution_count": 212,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 按列（axis=0）最大值的 Index\n",
    "np.argmax(arr, axis=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 213,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([1, 0, 0])"
      ]
     },
     "execution_count": 213,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 按行（axis=1）最小值的 Index\n",
    "np.argmin(arr, axis=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### `np.argsort`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 215,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[1, 3, 0, 2],\n",
       "       [0, 2, 3, 1],\n",
       "       [0, 2, 1, 3]])"
      ]
     },
     "execution_count": 215,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 默认按行（axis=1）排序的索引\n",
    "np.argsort(arr)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 216,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[2 0 3 1]\n",
      " [1 3 2 0]\n",
      " [3 1 2 0]]\n",
      "[[1 3 0 2]\n",
      " [0 2 3 1]\n",
      " [0 2 1 3]]\n"
     ]
    }
   ],
   "source": [
    "# 数据按行（axis=1）排序的索引，同上\n",
    "print(np.argsort(-arr, axis=1))\n",
    "print(np.argsort(arr, axis=1))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 217,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[1, 0, 2, 0],\n",
       "       [2, 2, 1, 1],\n",
       "       [0, 1, 0, 2]])"
      ]
     },
     "execution_count": 217,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 数据按列（axis=0）排序索引\n",
    "np.argsort(arr, axis=0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 矩阵和运算\n",
    "\n",
    "这一节我们将聚焦矩阵和相关的运算，主要包括：\n",
    "\n",
    "- 算术（四则运算及其他基础算术）\n",
    "- 广播\n",
    "- 矩阵相关\n",
    "\n",
    "这些内容其实使用非常普遍，普遍到我们甚至都不会察觉到自己在使用，而且也非常简单。当然，高纬度的计算我们这里并不涉及，但逻辑是一致的，只是更加复杂。\n",
    "\n",
    "### 算术\n",
    "\n",
    "⭐⭐⭐⭐\n",
    "\n",
    "所有的算术函数均可直接运用于 array。\n",
    "\n",
    "⚠️ 需要注意的是：`mod` 运算可以指定多个被除数。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 218,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 2, 15, 13,  9],\n",
       "       [ 9, 17,  2, 14],\n",
       "       [ 4,  2, 11, 19]])"
      ]
     },
     "execution_count": 218,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "rng = np.random.default_rng(42)\n",
    "arr = rng.integers(1, 20, (3, 4))\n",
    "arr"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 219,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 4, 30, 26, 18],\n",
       "       [18, 34,  4, 28],\n",
       "       [ 8,  4, 22, 38]])"
      ]
     },
     "execution_count": 219,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# +-*/ 四则运算，就跟两个数字计算一样\n",
    "arr * 2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 220,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[  4, 225, 169,  81],\n",
       "       [ 81, 289,   4, 196],\n",
       "       [ 16,   4, 121, 361]])"
      ]
     },
     "execution_count": 220,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 平方也可以\n",
    "arr ** 2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 221,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[1.41421356, 3.87298335, 3.60555128, 3.        ],\n",
       "       [3.        , 4.12310563, 1.41421356, 3.74165739],\n",
       "       [2.        , 1.41421356, 3.31662479, 4.35889894]])"
      ]
     },
     "execution_count": 221,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 开方\n",
    "np.sqrt(arr)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 222,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0.69314718, 2.7080502 , 2.56494936, 2.19722458],\n",
       "       [2.19722458, 2.83321334, 0.69314718, 2.63905733],\n",
       "       [1.38629436, 0.69314718, 2.39789527, 2.94443898]])"
      ]
     },
     "execution_count": 222,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# log\n",
    "np.log(arr)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 223,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[2, 5, 5, 5],\n",
       "       [5, 5, 2, 5],\n",
       "       [4, 2, 5, 5]])"
      ]
     },
     "execution_count": 223,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 超过5的都换成5\n",
    "np.minimum(arr, 5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 224,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 5, 15, 13,  9],\n",
       "       [ 9, 17,  5, 14],\n",
       "       [ 5,  5, 11, 19]])"
      ]
     },
     "execution_count": 224,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 低于5的都换成5\n",
    "np.maximum(arr, 5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 225,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[1.41, 3.87, 3.61, 3.  ],\n",
       "       [3.  , 4.12, 1.41, 3.74],\n",
       "       [2.  , 1.41, 3.32, 4.36]])"
      ]
     },
     "execution_count": 225,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 四舍五入\n",
    "np.round(np.sqrt(arr), 2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 226,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[1., 3., 3., 3.],\n",
       "       [3., 4., 1., 3.],\n",
       "       [2., 1., 3., 4.]])"
      ]
     },
     "execution_count": 226,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# floor/ceil\n",
    "np.floor(np.sqrt(arr))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 227,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[2., 4., 4., 3.],\n",
       "       [3., 5., 2., 4.],\n",
       "       [2., 2., 4., 5.]])"
      ]
     },
     "execution_count": 227,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.ceil(np.sqrt(arr))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 228,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[2, 0, 1, 0],\n",
       "       [0, 2, 2, 2],\n",
       "       [1, 2, 2, 1]])"
      ]
     },
     "execution_count": 228,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# mod <=> x % 3\n",
    "np.mod(arr, 3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 229,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[-3, 10,  8,  4],\n",
       "       [ 4, 12, -3,  9],\n",
       "       [-1, -3,  6, 14]])"
      ]
     },
     "execution_count": 229,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "arr - 5"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 230,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[-1,  5,  5,  1],\n",
       "       [ 1,  5, -1,  5],\n",
       "       [ 0, -1,  5,  5]])"
      ]
     },
     "execution_count": 230,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 还可以使用多个被除数\n",
    "np.mod(arr, arr - 5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 矩阵\n",
    "\n",
    "⭐⭐⭐⭐⭐\n",
    "\n",
    "这一小节主要介绍线性代数中矩阵的处理，我们会介绍几个矩阵相关常用的 API。\n",
    "\n",
    "⚠️ 需要注意的是：`dot` 和 `matmul` 在高维度时表现不同。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 231,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(array([[1, 7],\n",
       "        [6, 4],\n",
       "        [4, 8]]),\n",
       " array([[1, 7, 2, 1],\n",
       "        [5, 9, 7, 7]]),\n",
       " array([[7, 8, 5],\n",
       "        [2, 8, 5],\n",
       "        [5, 4, 2]]))"
      ]
     },
     "execution_count": 231,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "rng = np.random.default_rng(42)\n",
    "a = rng.integers(1, 10, (3, 2))\n",
    "b = rng.integers(1, 10, (2, 4))\n",
    "c = rng.integers(1, 10, (3, 3))\n",
    "a, b, c"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 232,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 36,  70,  51,  50],\n",
       "       [ 26,  78,  40,  34],\n",
       "       [ 44, 100,  64,  60]])"
      ]
     },
     "execution_count": 232,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# array 乘法\n",
    "# 矩阵乘法\n",
    "np.dot(a, b)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 235,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "-----------------------------------------------------\n",
      "[[[1 7 6]\n",
      "  [4 4 8]\n",
      "  [1 7 2]]\n",
      "\n",
      " [[1 5 9]\n",
      "  [7 7 7]\n",
      "  [8 5 2]]]\n",
      "-----------------------------------------------------\n",
      "[[[8 5 5 4]\n",
      "  [2 9 8 6]\n",
      "  [4 8 5 4]]\n",
      "\n",
      " [[5 3 1 5]\n",
      "  [8 1 8 8]\n",
      "  [3 6 2 7]]]\n",
      "-----------------------------------------------------\n",
      "[[[[ 46 116  91  70]\n",
      "   [ 79  46  69 103]]\n",
      "\n",
      "  [[ 72 120  92  72]\n",
      "   [ 76  64  52 108]]\n",
      "\n",
      "  [[ 30  84  71  54]\n",
      "   [ 67  22  61  75]]]\n",
      "\n",
      "\n",
      " [[[ 54 122  90  70]\n",
      "   [ 72  62  59 108]]\n",
      "\n",
      "  [[ 98 154 126  98]\n",
      "   [112  70  77 140]]\n",
      "\n",
      "  [[ 82 101  90  70]\n",
      "   [ 86  41  52  94]]]] (2, 3, 2, 4)\n",
      "-----------------------------------------------------\n",
      "[[[ 46 116  91  70]\n",
      "  [ 72 120  92  72]\n",
      "  [ 30  84  71  54]]\n",
      "\n",
      " [[ 72  62  59 108]\n",
      "  [112  70  77 140]\n",
      "  [ 86  41  52  94]]] (2, 3, 4)\n",
      "-----------------------------------------------------\n"
     ]
    }
   ],
   "source": [
    "# 我们看下高维度下 dot 和 matmul 的区别\n",
    "# ijk, lkm -> ijlm\n",
    "rng = np.random.default_rng(42)\n",
    "a = rng.integers(1, 10, (2, 3, 3))\n",
    "b = rng.integers(1, 10, (2, 3, 4))\n",
    "print(\"-----------------------------------------------------\")\n",
    "print(a)\n",
    "print(\"-----------------------------------------------------\")\n",
    "print(b)\n",
    "print(\"-----------------------------------------------------\")\n",
    "c = np.dot(a, b)\n",
    "d = np.matmul(a, b)\n",
    "print(c, c.shape)\n",
    "print(\"-----------------------------------------------------\")\n",
    "print(d, d.shape)\n",
    "print(\"-----------------------------------------------------\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.8"
  },
  "latex_envs": {
   "LaTeX_envs_menu_present": true,
   "autoclose": false,
   "autocomplete": true,
   "bibliofile": "biblio.bib",
   "cite_by": "apalike",
   "current_citInitial": 1,
   "eqLabelWithNumbers": true,
   "eqNumInitial": 1,
   "hotkeys": {
    "equation": "Ctrl-E",
    "itemize": "Ctrl-I"
   },
   "labels_anchors": false,
   "latex_user_defs": false,
   "report_style_numbering": false,
   "user_envs_cfg": false
  },
  "nbTranslate": {
   "displayLangs": [
    "*"
   ],
   "hotkey": "alt-t",
   "langInMainMenu": true,
   "sourceLang": "en",
   "targetLang": "fr",
   "useGoogleTranslate": true
  },
  "vscode": {
   "interpreter": {
    "hash": "dabd46d0b105194b6037154245f7bac3849b5735b43b605ee603f76a866d6105"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
