{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "36a47af1",
   "metadata": {},
   "source": [
    "# 第 9 讲｜pandas 表格基础：Series 与 DataFrame\n",
    "\n",
    "**参考教材：** `da_5f_pandas.pdf`  \n",
    "**适用基础：** 已学过列表、字典、条件判断和 NumPy 数组。  \n",
    "**本讲目标：** 使用 Series 和 DataFrame 表示表格数据，并完成选择、筛选、修改、缺失值处理、排序与统计。\n",
    "\n",
    "pandas 专门处理带有行标签和列标签的表格数据。它允许不同列使用不同数据类型，也是后续读取 CSV、数据清洗、分组统计和可视化的重要基础。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6b5e8b4a",
   "metadata": {},
   "source": [
    "## 训练路线\n",
    "\n",
    "1. 认识 Series、DataFrame 和索引标签；\n",
    "2. 明确单列、多列、标签位置和整数位置的不同选择方法；\n",
    "3. 使用布尔条件与 `isin()` 筛选数据；\n",
    "4. 安全地创建列、修改数据和处理缺失值；\n",
    "5. 完成删除、排序、统计、概览和频数分析。\n",
    "\n",
    "每个知识点均按“示例代码 → 对应解读 → 动手练习”展开。练习单元格保留了空白，补全后再运行。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c3a62ab4",
   "metadata": {},
   "source": [
    "## 教材内容取舍\n",
    "\n",
    "本课件使用 Conda `skills` 环境中的 pandas 3.0 运行验证。教材中最常用且适合当前阶段的 Series、DataFrame、`loc`、`iloc`、筛选、缺失值、排序和统计被放入主线。\n",
    "\n",
    "索引集合运算、复杂 `reindex`、广播细节、高阶 `apply`、排名方法、相关系数矩阵和重复标签等内容放在末尾“阅读拓展”。修改数据统一使用单次 `.loc[...] = ...`，避免容易出错的链式赋值。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6a7db2e7",
   "metadata": {},
   "source": [
    "## 1. 用 pd.Series 创建一维带标签数据\n",
    "\n",
    "`import pandas as pd` 导入 pandas，并把模块简称为 `pd`。\n",
    "\n",
    "`pd.Series(数据)` 创建一维带标签数据。没有指定标签时，pandas 自动使用从 0 开始的整数索引。`.index` 是 Series 的索引属性，末尾不加括号。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4acaa26f",
   "metadata": {},
   "source": [
    "### 示例代码"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "0b19feb9",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-30T13:01:32.634971Z",
     "iopub.status.busy": "2026-07-30T13:01:32.634761Z",
     "iopub.status.idle": "2026-07-30T13:01:33.742278Z",
     "shell.execute_reply": "2026-07-30T13:01:33.741694Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Series：\n",
      "0    72\n",
      "1    85\n",
      "2    90\n",
      "3    68\n",
      "dtype: int64\n",
      "自动生成的索引： RangeIndex(start=0, stop=4, step=1)\n"
     ]
    }
   ],
   "source": [
    "import pandas as pd\n",
    "\n",
    "scores = pd.Series([72, 85, 90, 68])\n",
    "\n",
    "print(\"Series：\")\n",
    "print(scores)\n",
    "print(\"自动生成的索引：\", scores.index)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "04278f73",
   "metadata": {},
   "source": [
    "### 代码解读（对应上方示例）\n",
    "\n",
    "- 左边的 `0、1、2、3` 是索引，右边是成绩值。\n",
    "- `pd.Series([72, 85, 90, 68])` 没有传入自定义标签，所以使用默认整数索引。\n",
    "- 最后一行的 `dtype` 表示这组值采用的数据类型，它不是额外的一行数据。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "61d1c04c",
   "metadata": {},
   "source": [
    "### 动手练习"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "e778c516",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-30T13:01:33.752172Z",
     "iopub.status.busy": "2026-07-30T13:01:33.752004Z",
     "iopub.status.idle": "2026-07-30T13:01:33.754606Z",
     "shell.execute_reply": "2026-07-30T13:01:33.753896Z"
    }
   },
   "outputs": [],
   "source": [
    "# 用 120、135、98 创建一个 Series，并打印它的索引\n",
    "# sales = __________\n",
    "# print(sales)\n",
    "# print(__________)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "de39ed4c",
   "metadata": {},
   "source": [
    "## 2. Series 的 loc 标签选择与 iloc 位置选择\n",
    "\n",
    "创建 Series 时可以使用 `index=` 指定标签。\n",
    "\n",
    "- `.loc[标签]` 按标签选择；\n",
    "- `.iloc[整数位置]` 按从 0 开始的位置选择。\n",
    "\n",
    "`loc` 和 `iloc` 是索引器，后面使用方括号，不写成函数调用。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "886157ad",
   "metadata": {},
   "source": [
    "### 示例代码"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "01317918",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-30T13:01:33.756597Z",
     "iopub.status.busy": "2026-07-30T13:01:33.756471Z",
     "iopub.status.idle": "2026-07-30T13:01:33.773305Z",
     "shell.execute_reply": "2026-07-30T13:01:33.772764Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "标签为乙的成绩： 85\n",
      "第 2 个位置的成绩： 85\n",
      "从甲到乙（包含乙）：\n",
      "甲    72\n",
      "乙    85\n",
      "dtype: int64\n"
     ]
    }
   ],
   "source": [
    "scores = pd.Series(\n",
    "    [72, 85, 90],\n",
    "    index=[\"甲\", \"乙\", \"丙\"]\n",
    ")\n",
    "\n",
    "print(\"标签为乙的成绩：\", scores.loc[\"乙\"])\n",
    "print(\"第 2 个位置的成绩：\", scores.iloc[1])\n",
    "print(\"从甲到乙（包含乙）：\")\n",
    "print(scores.loc[\"甲\":\"乙\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "02dec765",
   "metadata": {},
   "source": [
    "### 代码解读（对应上方示例）\n",
    "\n",
    "- `scores.loc[\"乙\"]` 中的“乙”是索引标签。\n",
    "- `scores.iloc[1]` 中的 `1` 是整数位置，两者都取得 85。\n",
    "- 与普通 Python 切片不同，`loc[\"甲\":\"乙\"]` 的标签切片包含末端“乙”。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ddce68a9",
   "metadata": {},
   "source": [
    "### 动手练习"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "6c42ed32",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-30T13:01:33.775539Z",
     "iopub.status.busy": "2026-07-30T13:01:33.775403Z",
     "iopub.status.idle": "2026-07-30T13:01:33.777654Z",
     "shell.execute_reply": "2026-07-30T13:01:33.777115Z"
    }
   },
   "outputs": [],
   "source": [
    "# 创建带有 A、B、C 标签的销量 Series\n",
    "# sales = pd.Series([100, 120, 90], index=__________)\n",
    "# 分别用 loc 和 iloc 取出 120\n",
    "# print(__________)\n",
    "# print(__________)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ee27784c",
   "metadata": {},
   "source": [
    "## 3. Series 运算按索引自动对齐\n",
    "\n",
    "两个 Series 做算术运算时，pandas 先按索引标签对齐。只有一边存在的标签会得到缺失值 `NaN`。\n",
    "\n",
    "`.add(另一个Series, fill_value=填充值)` 也执行加法，但可以先为缺少的一边填入指定值。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fe5ce138",
   "metadata": {},
   "source": [
    "### 示例代码"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "5abda390",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-30T13:01:33.779457Z",
     "iopub.status.busy": "2026-07-30T13:01:33.779267Z",
     "iopub.status.idle": "2026-07-30T13:01:33.794332Z",
     "shell.execute_reply": "2026-07-30T13:01:33.793579Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "普通相加：\n",
      "A     NaN\n",
      "B    15.0\n",
      "C     NaN\n",
      "dtype: float64\n",
      "缺失位置先按 0 处理：\n",
      "A    10.0\n",
      "B    15.0\n",
      "C     4.0\n",
      "dtype: float64\n"
     ]
    }
   ],
   "source": [
    "online = pd.Series([10, 12], index=[\"A\", \"B\"])\n",
    "offline = pd.Series([3, 4], index=[\"B\", \"C\"])\n",
    "\n",
    "normal_total = online + offline\n",
    "filled_total = online.add(offline, fill_value=0)\n",
    "\n",
    "print(\"普通相加：\")\n",
    "print(normal_total)\n",
    "print(\"缺失位置先按 0 处理：\")\n",
    "print(filled_total)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c883c7b4",
   "metadata": {},
   "source": [
    "### 代码解读（对应上方示例）\n",
    "\n",
    "- 标签 B 在两边都存在，所以普通相加得到 `12 + 3 = 15`。\n",
    "- 标签 A 和 C 只在一边存在，普通相加时结果是 `NaN`。\n",
    "- `add(..., fill_value=0)` 把缺少的一边按 0 处理，因此 A、B、C 都得到数值结果。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d610323b",
   "metadata": {},
   "source": [
    "### 动手练习"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "aad86445",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-30T13:01:33.796350Z",
     "iopub.status.busy": "2026-07-30T13:01:33.796203Z",
     "iopub.status.idle": "2026-07-30T13:01:33.808217Z",
     "shell.execute_reply": "2026-07-30T13:01:33.806883Z"
    }
   },
   "outputs": [],
   "source": [
    "# 两个 Series 按标签相加，缺少的位置按 0 处理\n",
    "# first = pd.Series([5, 8], index=[\"甲\", \"乙\"])\n",
    "# second = pd.Series([2, 4], index=[\"乙\", \"丙\"])\n",
    "# result = __________\n",
    "# print(result)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a493af21",
   "metadata": {},
   "source": [
    "## 4. 用 pd.DataFrame 创建二维表格\n",
    "\n",
    "`pd.DataFrame(字典)` 可以把字典创建为二维表格：字典的键成为列名，每个等长列表成为一列。\n",
    "\n",
    "DataFrame 同时具有行索引和列索引，各列可以保存不同的数据类型。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "366bde44",
   "metadata": {},
   "source": [
    "### 示例代码"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "e34747a9",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-30T13:01:33.809948Z",
     "iopub.status.busy": "2026-07-30T13:01:33.809813Z",
     "iopub.status.idle": "2026-07-30T13:01:33.817680Z",
     "shell.execute_reply": "2026-07-30T13:01:33.816905Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "   姓名  方向  成绩\n",
      "0  小林  数据  82\n",
      "1  小周  金融  76\n",
      "2  小郑  数据  91\n",
      "3  小王  营销  68\n"
     ]
    }
   ],
   "source": [
    "data = {\n",
    "    \"姓名\": [\"小林\", \"小周\", \"小郑\", \"小王\"],\n",
    "    \"方向\": [\"数据\", \"金融\", \"数据\", \"营销\"],\n",
    "    \"成绩\": [82, 76, 91, 68]\n",
    "}\n",
    "students = pd.DataFrame(data)\n",
    "\n",
    "print(students)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "db13b55e",
   "metadata": {},
   "source": [
    "### 代码解读（对应上方示例）\n",
    "\n",
    "- 三个字典键“姓名”“方向”“成绩”成为三列的列名。\n",
    "- 三个列表长度都为 4，因此表格有 4 行。\n",
    "- pandas 自动创建 `0、1、2、3` 作为行索引。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dce13ac6",
   "metadata": {},
   "source": [
    "### 动手练习"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "963654a0",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-30T13:01:33.820627Z",
     "iopub.status.busy": "2026-07-30T13:01:33.820438Z",
     "iopub.status.idle": "2026-07-30T13:01:33.822972Z",
     "shell.execute_reply": "2026-07-30T13:01:33.822283Z"
    }
   },
   "outputs": [],
   "source": [
    "# 用字典创建包含“商品”“单价”“数量”三列的 DataFrame\n",
    "# data = {\n",
    "#     \"商品\": __________,\n",
    "#     \"单价\": __________,\n",
    "#     \"数量\": __________\n",
    "# }\n",
    "# goods = __________\n",
    "# print(goods)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1eb99212",
   "metadata": {},
   "source": [
    "## 5. 快速了解 DataFrame：head、shape、columns 与 dtypes\n",
    "\n",
    "获得一张表后，先用以下工具检查结构：\n",
    "\n",
    "- `.head(n)`：查看前 n 行；\n",
    "- `.shape`：查看 `(行数, 列数)`；\n",
    "- `.columns`：查看列名；\n",
    "- `.dtypes`：查看每一列的数据类型。\n",
    "\n",
    "`shape`、`columns`、`dtypes` 是属性，不加括号；`head` 是方法，需要括号。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "31624d1e",
   "metadata": {},
   "source": [
    "### 示例代码"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "c7d7c9ad",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-30T13:01:33.824948Z",
     "iopub.status.busy": "2026-07-30T13:01:33.824807Z",
     "iopub.status.idle": "2026-07-30T13:01:33.829834Z",
     "shell.execute_reply": "2026-07-30T13:01:33.829284Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "前 2 行：\n",
      "   姓名  方向  成绩\n",
      "0  小林  数据  82\n",
      "1  小周  金融  76\n",
      "形状： (4, 3)\n",
      "列名： Index(['姓名', '方向', '成绩'], dtype='str')\n",
      "各列类型：\n",
      "姓名      str\n",
      "方向      str\n",
      "成绩    int64\n",
      "dtype: object\n"
     ]
    }
   ],
   "source": [
    "students = pd.DataFrame({\n",
    "    \"姓名\": [\"小林\", \"小周\", \"小郑\", \"小王\"],\n",
    "    \"方向\": [\"数据\", \"金融\", \"数据\", \"营销\"],\n",
    "    \"成绩\": [82, 76, 91, 68]\n",
    "})\n",
    "\n",
    "print(\"前 2 行：\")\n",
    "print(students.head(2))\n",
    "print(\"形状：\", students.shape)\n",
    "print(\"列名：\", students.columns)\n",
    "print(\"各列类型：\")\n",
    "print(students.dtypes)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3754fb07",
   "metadata": {},
   "source": [
    "### 代码解读（对应上方示例）\n",
    "\n",
    "- `head(2)` 只显示前两行，不会删除其余行。\n",
    "- `shape` 是 `(4, 3)`，表示 4 行 3 列。\n",
    "- `columns` 返回三列的名称。\n",
    "- `dtypes` 分别报告文本列和整数列采用的数据类型。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7d5d76f5",
   "metadata": {},
   "source": [
    "### 动手练习"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "31664baa",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-30T13:01:33.831721Z",
     "iopub.status.busy": "2026-07-30T13:01:33.831562Z",
     "iopub.status.idle": "2026-07-30T13:01:33.833852Z",
     "shell.execute_reply": "2026-07-30T13:01:33.833423Z"
    }
   },
   "outputs": [],
   "source": [
    "# 创建一张小表，然后查看前 3 行、形状和列名\n",
    "# table = pd.DataFrame({\n",
    "#     \"城市\": [\"A\", \"B\", \"C\", \"D\"],\n",
    "#     \"销量\": [80, 95, 100, 120]\n",
    "# })\n",
    "# print(__________)\n",
    "# print(__________)\n",
    "# print(__________)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4e681e4e",
   "metadata": {},
   "source": [
    "## 6. 使用方括号选择单列或多列\n",
    "\n",
    "DataFrame 使用方括号选择列：\n",
    "\n",
    "- `df[\"列名\"]` 选择一列，结果是 Series；\n",
    "- `df[[\"列名1\", \"列名2\"]]` 选择多列，结果是 DataFrame。\n",
    "\n",
    "多列写法的内层列表只负责列出要选择的列名，也决定结果中的列顺序。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "16edeb17",
   "metadata": {},
   "source": [
    "### 示例代码"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "09f3bebd",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-30T13:01:33.835409Z",
     "iopub.status.busy": "2026-07-30T13:01:33.835293Z",
     "iopub.status.idle": "2026-07-30T13:01:33.845275Z",
     "shell.execute_reply": "2026-07-30T13:01:33.844736Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "选择一列：\n",
      "0    82\n",
      "1    76\n",
      "2    91\n",
      "Name: 成绩, dtype: int64\n",
      "按指定顺序选择两列：\n",
      "   成绩  姓名\n",
      "0  82  小林\n",
      "1  76  小周\n",
      "2  91  小郑\n"
     ]
    }
   ],
   "source": [
    "students = pd.DataFrame({\n",
    "    \"姓名\": [\"小林\", \"小周\", \"小郑\"],\n",
    "    \"方向\": [\"数据\", \"金融\", \"数据\"],\n",
    "    \"成绩\": [82, 76, 91]\n",
    "})\n",
    "\n",
    "one_column = students[\"成绩\"]\n",
    "two_columns = students[[\"成绩\", \"姓名\"]]\n",
    "\n",
    "print(\"选择一列：\")\n",
    "print(one_column)\n",
    "print(\"按指定顺序选择两列：\")\n",
    "print(two_columns)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "466c0af5",
   "metadata": {},
   "source": [
    "### 代码解读（对应上方示例）\n",
    "\n",
    "- `students[\"成绩\"]` 只用一个列名，得到一维 Series。\n",
    "- `students[[\"成绩\", \"姓名\"]]` 把两个列名放在列表中，得到二维 DataFrame。\n",
    "- 结果先显示“成绩”再显示“姓名”，因为内层列表采用了这个顺序。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e3e042cd",
   "metadata": {},
   "source": [
    "### 动手练习"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "829a82cc",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-30T13:01:33.846816Z",
     "iopub.status.busy": "2026-07-30T13:01:33.846711Z",
     "iopub.status.idle": "2026-07-30T13:01:33.848905Z",
     "shell.execute_reply": "2026-07-30T13:01:33.848383Z"
    }
   },
   "outputs": [],
   "source": [
    "# 从下表中先选择“数量”，再选择“商品”和“单价”两列\n",
    "# goods = pd.DataFrame({\n",
    "#     \"商品\": [\"A\", \"B\"], \"单价\": [8, 12], \"数量\": [3, 5]\n",
    "# })\n",
    "# quantity = __________\n",
    "# selected = __________\n",
    "# print(quantity)\n",
    "# print(selected)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "339d0272",
   "metadata": {},
   "source": [
    "## 7. 用 loc 按行列标签选择数据\n",
    "\n",
    "DataFrame 的 `.loc[行标签, 列标签]` 按名称选择数据。行或列需要多个标签时，把标签放进列表；冒号 `:` 表示该方向全部保留。\n",
    "\n",
    "`loc` 只认标签，不把整数自动解释为位置。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7cba36ee",
   "metadata": {},
   "source": [
    "### 示例代码"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "47c76f49",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-30T13:01:33.850442Z",
     "iopub.status.busy": "2026-07-30T13:01:33.850330Z",
     "iopub.status.idle": "2026-07-30T13:01:33.856133Z",
     "shell.execute_reply": "2026-07-30T13:01:33.855586Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "标签 S02 对应的一行：\n",
      "姓名    小周\n",
      "方向    金融\n",
      "成绩    76\n",
      "Name: S02, dtype: object\n",
      "指定行和指定列：\n",
      "     姓名  成绩\n",
      "S01  小林  82\n",
      "S03  小郑  91\n",
      "所有行的成绩列：\n",
      "S01    82\n",
      "S02    76\n",
      "S03    91\n",
      "Name: 成绩, dtype: int64\n"
     ]
    }
   ],
   "source": [
    "students = pd.DataFrame(\n",
    "    {\n",
    "        \"姓名\": [\"小林\", \"小周\", \"小郑\"],\n",
    "        \"方向\": [\"数据\", \"金融\", \"数据\"],\n",
    "        \"成绩\": [82, 76, 91]\n",
    "    },\n",
    "    index=[\"S01\", \"S02\", \"S03\"]\n",
    ")\n",
    "\n",
    "print(\"标签 S02 对应的一行：\")\n",
    "print(students.loc[\"S02\"])\n",
    "print(\"指定行和指定列：\")\n",
    "print(students.loc[[\"S01\", \"S03\"], [\"姓名\", \"成绩\"]])\n",
    "print(\"所有行的成绩列：\")\n",
    "print(students.loc[:, \"成绩\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e3c9b918",
   "metadata": {},
   "source": [
    "### 代码解读（对应上方示例）\n",
    "\n",
    "- `students.loc[\"S02\"]` 使用行标签选择一行。\n",
    "- 第一个列表指定 S01、S03 两行，第二个列表指定“姓名”“成绩”两列。\n",
    "- `students.loc[:, \"成绩\"]` 中的冒号表示保留所有行。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c923862d",
   "metadata": {},
   "source": [
    "### 动手练习"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "0ec05019",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-30T13:01:33.858009Z",
     "iopub.status.busy": "2026-07-30T13:01:33.857875Z",
     "iopub.status.idle": "2026-07-30T13:01:33.860034Z",
     "shell.execute_reply": "2026-07-30T13:01:33.859576Z"
    }
   },
   "outputs": [],
   "source": [
    "# 用 loc 选出编号 B、C 两行的“商品”和“数量”列\n",
    "# goods = pd.DataFrame(\n",
    "#     {\"商品\": [\"甲\", \"乙\", \"丙\"], \"单价\": [8, 12, 15], \"数量\": [3, 5, 2]},\n",
    "#     index=[\"A\", \"B\", \"C\"]\n",
    "# )\n",
    "# result = __________\n",
    "# print(result)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d6b1ba63",
   "metadata": {},
   "source": [
    "## 8. 用 iloc 按整数位置选择数据\n",
    "\n",
    "DataFrame 的 `.iloc[行位置, 列位置]` 只按从 0 开始的整数位置选择数据。\n",
    "\n",
    "`iloc` 的切片和普通 Python 切片一样：包含起点，不包含终点。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a990cac5",
   "metadata": {},
   "source": [
    "### 示例代码"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "2eefa717",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-30T13:01:33.861634Z",
     "iopub.status.busy": "2026-07-30T13:01:33.861516Z",
     "iopub.status.idle": "2026-07-30T13:01:33.866326Z",
     "shell.execute_reply": "2026-07-30T13:01:33.865714Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "第 2 行：\n",
      "姓名    小周\n",
      "方向    金融\n",
      "成绩    76\n",
      "Name: 1, dtype: object\n",
      "前 2 行、前 2 列：\n",
      "   姓名  方向\n",
      "0  小林  数据\n",
      "1  小周  金融\n",
      "最后一行的最后一列： 91\n"
     ]
    }
   ],
   "source": [
    "students = pd.DataFrame({\n",
    "    \"姓名\": [\"小林\", \"小周\", \"小郑\"],\n",
    "    \"方向\": [\"数据\", \"金融\", \"数据\"],\n",
    "    \"成绩\": [82, 76, 91]\n",
    "})\n",
    "\n",
    "print(\"第 2 行：\")\n",
    "print(students.iloc[1])\n",
    "print(\"前 2 行、前 2 列：\")\n",
    "print(students.iloc[0:2, 0:2])\n",
    "print(\"最后一行的最后一列：\", students.iloc[-1, -1])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9e827a01",
   "metadata": {},
   "source": [
    "### 代码解读（对应上方示例）\n",
    "\n",
    "- `students.iloc[1]` 按位置取得第 2 行。\n",
    "- `students.iloc[0:2, 0:2]` 取得行位置 0、1 和列位置 0、1。\n",
    "- `-1` 表示最后一个位置，所以最后一项是 91。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c3c1d7d0",
   "metadata": {},
   "source": [
    "### 动手练习"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "a0094b90",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-30T13:01:33.868000Z",
     "iopub.status.busy": "2026-07-30T13:01:33.867882Z",
     "iopub.status.idle": "2026-07-30T13:01:33.870049Z",
     "shell.execute_reply": "2026-07-30T13:01:33.869504Z"
    }
   },
   "outputs": [],
   "source": [
    "# 用 iloc 取出下表后两行的前两列\n",
    "# table = pd.DataFrame({\n",
    "#     \"A\": [1, 2, 3], \"B\": [4, 5, 6], \"C\": [7, 8, 9]\n",
    "# })\n",
    "# result = __________\n",
    "# print(result)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4350f61c",
   "metadata": {},
   "source": [
    "## 9. 使用布尔条件筛选行\n",
    "\n",
    "一列与条件比较后，会得到由 `True` 和 `False` 组成的布尔 Series。把它放入 `df[条件]`，即可保留条件为 `True` 的整行。\n",
    "\n",
    "推荐先把条件保存在名称清楚的变量中，便于检查。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2d7f84d6",
   "metadata": {},
   "source": [
    "### 示例代码"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "888946ac",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-30T13:01:33.871560Z",
     "iopub.status.busy": "2026-07-30T13:01:33.871444Z",
     "iopub.status.idle": "2026-07-30T13:01:33.876748Z",
     "shell.execute_reply": "2026-07-30T13:01:33.876180Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "布尔条件：\n",
      "0     True\n",
      "1    False\n",
      "2     True\n",
      "3    False\n",
      "Name: 成绩, dtype: bool\n",
      "成绩至少 80 分的行：\n",
      "   姓名  成绩\n",
      "0  小林  82\n",
      "2  小郑  91\n"
     ]
    }
   ],
   "source": [
    "students = pd.DataFrame({\n",
    "    \"姓名\": [\"小林\", \"小周\", \"小郑\", \"小王\"],\n",
    "    \"成绩\": [82, 76, 91, 68]\n",
    "})\n",
    "\n",
    "high_score_mask = students[\"成绩\"] >= 80\n",
    "high_score_students = students[high_score_mask]\n",
    "\n",
    "print(\"布尔条件：\")\n",
    "print(high_score_mask)\n",
    "print(\"成绩至少 80 分的行：\")\n",
    "print(high_score_students)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2331a2f3",
   "metadata": {},
   "source": [
    "### 代码解读（对应上方示例）\n",
    "\n",
    "- `students[\"成绩\"] >= 80` 对成绩列逐行比较。\n",
    "- 第 0、2 行的条件为 `True`，因此筛选结果只保留小林和小郑。\n",
    "- 条件 Series 的索引与原表行索引对应，pandas 据此选择整行。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6f1235e5",
   "metadata": {},
   "source": [
    "### 动手练习"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "a4a7616d",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-30T13:01:33.878276Z",
     "iopub.status.busy": "2026-07-30T13:01:33.878165Z",
     "iopub.status.idle": "2026-07-30T13:01:33.880331Z",
     "shell.execute_reply": "2026-07-30T13:01:33.879780Z"
    }
   },
   "outputs": [],
   "source": [
    "# 筛选数量大于 3 的商品行\n",
    "# goods = pd.DataFrame({\n",
    "#     \"商品\": [\"A\", \"B\", \"C\"], \"数量\": [2, 5, 4]\n",
    "# })\n",
    "# mask = __________\n",
    "# result = __________\n",
    "# print(result)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2ae1db4d",
   "metadata": {},
   "source": [
    "## 10. 组合筛选条件，并用 isin 检查多个目标值\n",
    "\n",
    "多个数组条件使用 `&`（并且）、`|`（或者）、`~`（取反），每个比较条件都要加括号。\n",
    "\n",
    "`.isin(目标值列表)` 检查一列中的每个值是否属于给定列表，返回布尔 Series。目标值列表只是“允许出现哪些值”的清单。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5a8fd8c9",
   "metadata": {},
   "source": [
    "### 示例代码"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "05834560",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-30T13:01:33.882073Z",
     "iopub.status.busy": "2026-07-30T13:01:33.881949Z",
     "iopub.status.idle": "2026-07-30T13:01:33.890450Z",
     "shell.execute_reply": "2026-07-30T13:01:33.888866Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "方向是否在目标列表中：\n",
      "0     True\n",
      "1     True\n",
      "2     True\n",
      "3    False\n",
      "4    False\n",
      "Name: 方向, dtype: bool\n",
      "目标方向且成绩至少 80 分：\n",
      "   姓名  方向  成绩\n",
      "0  小林  数据  82\n",
      "2  小郑  数据  91\n"
     ]
    }
   ],
   "source": [
    "students = pd.DataFrame({\n",
    "    \"姓名\": [\"小林\", \"小周\", \"小郑\", \"小王\", \"小赵\"],\n",
    "    \"方向\": [\"数据\", \"金融\", \"数据\", \"营销\", \"设计\"],\n",
    "    \"成绩\": [82, 76, 91, 88, 85]\n",
    "})\n",
    "\n",
    "target_directions = [\"数据\", \"金融\"]\n",
    "direction_mask = students[\"方向\"].isin(target_directions)\n",
    "score_mask = students[\"成绩\"] >= 80\n",
    "selected = students[direction_mask & score_mask]\n",
    "\n",
    "print(\"方向是否在目标列表中：\")\n",
    "print(direction_mask)\n",
    "print(\"目标方向且成绩至少 80 分：\")\n",
    "print(selected)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9e20c85e",
   "metadata": {},
   "source": [
    "### 代码解读（对应上方示例）\n",
    "\n",
    "- `isin([\"数据\", \"金融\"])` 逐行检查“方向”是否等于这两个值中的任意一个。\n",
    "- `score_mask` 单独表示成绩至少 80 分。\n",
    "- `direction_mask & score_mask` 要求两项条件同时成立；这里保留小林和小郑。\n",
    "- `isin` 学的是多值成员检查，不负责排序，也不决定输出列顺序。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4007a58f",
   "metadata": {},
   "source": [
    "### 动手练习"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "efad6a31",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-30T13:01:33.892129Z",
     "iopub.status.busy": "2026-07-30T13:01:33.892008Z",
     "iopub.status.idle": "2026-07-30T13:01:33.894304Z",
     "shell.execute_reply": "2026-07-30T13:01:33.893709Z"
    }
   },
   "outputs": [],
   "source": [
    "# 筛选城市为杭州或宁波，并且销量至少 100 的行\n",
    "# sales = pd.DataFrame({\n",
    "#     \"城市\": [\"杭州\", \"宁波\", \"温州\", \"杭州\"],\n",
    "#     \"销量\": [90, 120, 130, 150]\n",
    "# })\n",
    "# city_mask = __________\n",
    "# sales_mask = __________\n",
    "# result = __________\n",
    "# print(result)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a6cc5779",
   "metadata": {},
   "source": [
    "## 11. 创建计算列，并用 loc 安全修改数据\n",
    "\n",
    "`df[\"新列\"] = 值` 可以创建新列。右侧是数组运算时，会逐行计算。\n",
    "\n",
    "按条件修改已有数据时，使用一次 `.loc[条件, \"列名\"] = 新值`。不要写成先筛选、再选择列的链式赋值。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "40ad900d",
   "metadata": {},
   "source": [
    "### 示例代码"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "5f9d16b5",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-30T13:01:33.896007Z",
     "iopub.status.busy": "2026-07-30T13:01:33.895903Z",
     "iopub.status.idle": "2026-07-30T13:01:33.902371Z",
     "shell.execute_reply": "2026-07-30T13:01:33.901780Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "   姓名  成绩  加分后 是否及格\n",
      "0  小林  82   87    是\n",
      "1  小周  56   61    否\n",
      "2  小郑  98  100    是\n",
      "3  小王  68   73    是\n"
     ]
    }
   ],
   "source": [
    "students = pd.DataFrame({\n",
    "    \"姓名\": [\"小林\", \"小周\", \"小郑\", \"小王\"],\n",
    "    \"成绩\": [82, 56, 98, 68]\n",
    "})\n",
    "\n",
    "students[\"加分后\"] = students[\"成绩\"] + 5\n",
    "students.loc[students[\"加分后\"] > 100, \"加分后\"] = 100\n",
    "\n",
    "students[\"是否及格\"] = \"是\"\n",
    "students.loc[students[\"成绩\"] < 60, \"是否及格\"] = \"否\"\n",
    "\n",
    "print(students)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aa221d7b",
   "metadata": {},
   "source": [
    "### 代码解读（对应上方示例）\n",
    "\n",
    "- 第一条赋值使用向量化运算，为每一行创建“加分后”结果。\n",
    "- 第二条赋值只修改加分后超过 100 的单元格。\n",
    "- 先把“是否及格”整列设为“是”，再用单次 `loc` 把不及格行改为“否”。\n",
    "- 这种写法明确指出要改哪些行、哪一列，避免链式赋值问题。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a492e2c0",
   "metadata": {},
   "source": [
    "### 动手练习"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "c72dc85e",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-30T13:01:33.903990Z",
     "iopub.status.busy": "2026-07-30T13:01:33.903875Z",
     "iopub.status.idle": "2026-07-30T13:01:33.906090Z",
     "shell.execute_reply": "2026-07-30T13:01:33.905502Z"
    }
   },
   "outputs": [],
   "source": [
    "# 新建“金额”列等于“单价”乘“数量”，再把超过 50 的金额改为 50\n",
    "# goods = pd.DataFrame({\n",
    "#     \"商品\": [\"A\", \"B\", \"C\"], \"单价\": [8, 12, 15], \"数量\": [3, 5, 2]\n",
    "# })\n",
    "# goods[\"金额\"] = __________\n",
    "# goods.loc[__________, \"金额\"] = 50\n",
    "# print(goods)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fc8be275",
   "metadata": {},
   "source": [
    "## 12. 缺失值：isna、fillna 与 dropna\n",
    "\n",
    "表格中的空缺通常显示为 `NaN`：\n",
    "\n",
    "- `.isna()`：判断每个位置是否缺失；\n",
    "- `.isna().sum()`：按列统计缺失值数量；\n",
    "- `.fillna(填充值)`：返回填充缺失值后的新对象；\n",
    "- `.dropna()`：返回删除含缺失值行后的新对象。\n",
    "\n",
    "原表不会被这些默认调用自动修改。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "97e92309",
   "metadata": {},
   "source": [
    "### 示例代码"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "ba22a59e",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-30T13:01:33.908218Z",
     "iopub.status.busy": "2026-07-30T13:01:33.908091Z",
     "iopub.status.idle": "2026-07-30T13:01:33.915039Z",
     "shell.execute_reply": "2026-07-30T13:01:33.914439Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "各列缺失数量：\n",
      "姓名    0\n",
      "城市    1\n",
      "成绩    1\n",
      "dtype: int64\n",
      "填充后：\n",
      "   姓名  城市    成绩\n",
      "0  小林  杭州  82.0\n",
      "1  小周  未知  76.0\n",
      "2  小郑  宁波   0.0\n",
      "3  小王  温州  68.0\n",
      "删除含缺失值的行后：\n",
      "   姓名  城市    成绩\n",
      "0  小林  杭州  82.0\n",
      "3  小王  温州  68.0\n"
     ]
    }
   ],
   "source": [
    "students = pd.DataFrame({\n",
    "    \"姓名\": [\"小林\", \"小周\", \"小郑\", \"小王\"],\n",
    "    \"城市\": [\"杭州\", None, \"宁波\", \"温州\"],\n",
    "    \"成绩\": [82, 76, None, 68]\n",
    "})\n",
    "\n",
    "missing_count = students.isna().sum()\n",
    "filled = students.fillna({\"城市\": \"未知\", \"成绩\": 0})\n",
    "complete = students.dropna()\n",
    "\n",
    "print(\"各列缺失数量：\")\n",
    "print(missing_count)\n",
    "print(\"填充后：\")\n",
    "print(filled)\n",
    "print(\"删除含缺失值的行后：\")\n",
    "print(complete)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e5d7863d",
   "metadata": {},
   "source": [
    "### 代码解读（对应上方示例）\n",
    "\n",
    "- `students.isna()` 先得到真假表格，随后 `.sum()` 按列统计 `True` 的数量。\n",
    "- `fillna` 使用字典为不同列指定不同填充值。\n",
    "- `dropna()` 只保留所有列都不缺失的行。\n",
    "- `filled`、`complete` 是新对象，原来的 `students` 仍保留缺失值。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2a503dd2",
   "metadata": {},
   "source": [
    "### 动手练习"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "0b842b24",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-30T13:01:33.916973Z",
     "iopub.status.busy": "2026-07-30T13:01:33.916836Z",
     "iopub.status.idle": "2026-07-30T13:01:33.919109Z",
     "shell.execute_reply": "2026-07-30T13:01:33.918547Z"
    }
   },
   "outputs": [],
   "source": [
    "# 统计下表每列缺失值数量，并把缺失销量填为 0\n",
    "# sales = pd.DataFrame({\n",
    "#     \"城市\": [\"A\", \"B\", None], \"销量\": [100, None, 120]\n",
    "# })\n",
    "# print(__________)\n",
    "# filled = __________\n",
    "# print(filled)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7d478040",
   "metadata": {},
   "source": [
    "## 13. 用 drop 删除指定行或列\n",
    "\n",
    "`.drop(index=行标签)` 删除指定行，`.drop(columns=列名)` 删除指定列。两个写法都默认返回新 DataFrame。\n",
    "\n",
    "传入多个标签时使用列表；即使只删除一列，也可以直接把列名字符串传给 `columns=`。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "57afc92f",
   "metadata": {},
   "source": [
    "### 示例代码"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "e4176f53",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-30T13:01:33.920745Z",
     "iopub.status.busy": "2026-07-30T13:01:33.920627Z",
     "iopub.status.idle": "2026-07-30T13:01:33.926927Z",
     "shell.execute_reply": "2026-07-30T13:01:33.926192Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "删除索引 1 对应的行：\n",
      "   姓名  成绩 备注\n",
      "0  小林  82  A\n",
      "2  小郑  91  A\n",
      "删除备注列：\n",
      "   姓名  成绩\n",
      "0  小林  82\n",
      "1  小周  76\n",
      "2  小郑  91\n",
      "原表仍有备注列：\n",
      "   姓名  成绩 备注\n",
      "0  小林  82  A\n",
      "1  小周  76  B\n",
      "2  小郑  91  A\n"
     ]
    }
   ],
   "source": [
    "students = pd.DataFrame({\n",
    "    \"姓名\": [\"小林\", \"小周\", \"小郑\"],\n",
    "    \"成绩\": [82, 76, 91],\n",
    "    \"备注\": [\"A\", \"B\", \"A\"]\n",
    "})\n",
    "\n",
    "without_row = students.drop(index=[1])\n",
    "without_column = students.drop(columns=\"备注\")\n",
    "\n",
    "print(\"删除索引 1 对应的行：\")\n",
    "print(without_row)\n",
    "print(\"删除备注列：\")\n",
    "print(without_column)\n",
    "print(\"原表仍有备注列：\")\n",
    "print(students)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ac49a273",
   "metadata": {},
   "source": [
    "### 代码解读（对应上方示例）\n",
    "\n",
    "- `drop(index=[1])` 删除行标签 1 对应的整行。\n",
    "- `drop(columns=\"备注\")` 删除“备注”列。\n",
    "- 两个结果保存在新变量中，最后打印原表可确认它没有被修改。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dc433c19",
   "metadata": {},
   "source": [
    "### 动手练习"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "5ab0b1cd",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-30T13:01:33.928771Z",
     "iopub.status.busy": "2026-07-30T13:01:33.928646Z",
     "iopub.status.idle": "2026-07-30T13:01:33.930823Z",
     "shell.execute_reply": "2026-07-30T13:01:33.930244Z"
    }
   },
   "outputs": [],
   "source": [
    "# 删除索引 0 对应的行，再删除“编号”列\n",
    "# table = pd.DataFrame({\n",
    "#     \"编号\": [1, 2, 3], \"商品\": [\"A\", \"B\", \"C\"], \"数量\": [3, 5, 2]\n",
    "# })\n",
    "# rows_removed = __________\n",
    "# column_removed = __________\n",
    "# print(rows_removed)\n",
    "# print(column_removed)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8d26826f",
   "metadata": {},
   "source": [
    "## 14. 用 sort_values 按列值排序\n",
    "\n",
    "`.sort_values(\"列名\")` 按指定列升序排列。传入 `ascending=False` 改为降序。\n",
    "\n",
    "排序返回新 DataFrame，原表的行索引会随对应行一起移动。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9171955a",
   "metadata": {},
   "source": [
    "### 示例代码"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "bec2a3c3",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-30T13:01:33.932799Z",
     "iopub.status.busy": "2026-07-30T13:01:33.932707Z",
     "iopub.status.idle": "2026-07-30T13:01:33.937292Z",
     "shell.execute_reply": "2026-07-30T13:01:33.936764Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "按成绩降序：\n",
      "   姓名  成绩\n",
      "2  小郑  91\n",
      "0  小林  82\n",
      "1  小周  76\n",
      "3  小王  68\n",
      "原表顺序：\n",
      "   姓名  成绩\n",
      "0  小林  82\n",
      "1  小周  76\n",
      "2  小郑  91\n",
      "3  小王  68\n"
     ]
    }
   ],
   "source": [
    "students = pd.DataFrame({\n",
    "    \"姓名\": [\"小林\", \"小周\", \"小郑\", \"小王\"],\n",
    "    \"成绩\": [82, 76, 91, 68]\n",
    "})\n",
    "\n",
    "descending = students.sort_values(\"成绩\", ascending=False)\n",
    "\n",
    "print(\"按成绩降序：\")\n",
    "print(descending)\n",
    "print(\"原表顺序：\")\n",
    "print(students)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4fd465f7",
   "metadata": {},
   "source": [
    "### 代码解读（对应上方示例）\n",
    "\n",
    "- `sort_values(\"成绩\", ascending=False)` 使用“成绩”列作为排序依据。\n",
    "- 最大成绩 91 排在第一行，最小成绩 68 排在最后一行。\n",
    "- 原索引 `0、1、2、3` 跟着各自的数据移动，因此排序后的索引不一定连续。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bcbaa0bc",
   "metadata": {},
   "source": [
    "### 动手练习"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "1062bdc8",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-30T13:01:33.938882Z",
     "iopub.status.busy": "2026-07-30T13:01:33.938770Z",
     "iopub.status.idle": "2026-07-30T13:01:33.941020Z",
     "shell.execute_reply": "2026-07-30T13:01:33.940326Z"
    }
   },
   "outputs": [],
   "source": [
    "# 按“销量”从小到大排序\n",
    "# sales = pd.DataFrame({\n",
    "#     \"城市\": [\"A\", \"B\", \"C\"], \"销量\": [120, 80, 100]\n",
    "# })\n",
    "# result = __________\n",
    "# print(result)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1997dd15",
   "metadata": {},
   "source": [
    "## 15. 统计汇总与 axis：sum、mean、max、idxmax\n",
    "\n",
    "常用统计方法包括 `.sum()`、`.mean()`、`.max()`。`.idxmax()` 返回最大值所在的索引标签。\n",
    "\n",
    "DataFrame 默认 `axis=0`，按行方向汇总并为每列得到结果；`axis=1` 表示跨列汇总，为每行得到结果。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3f0fe03f",
   "metadata": {},
   "source": [
    "### 示例代码"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "170456c0",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-30T13:01:33.942603Z",
     "iopub.status.busy": "2026-07-30T13:01:33.942491Z",
     "iopub.status.idle": "2026-07-30T13:01:33.947739Z",
     "shell.execute_reply": "2026-07-30T13:01:33.946882Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "各列总分：\n",
      "语文    247\n",
      "数学    251\n",
      "dtype: int64\n",
      "各列平均分：\n",
      "语文    82.333333\n",
      "数学    83.666667\n",
      "dtype: float64\n",
      "每位学生两科总分：\n",
      "小林    165\n",
      "小周    153\n",
      "小郑    180\n",
      "dtype: int64\n",
      "各科最高分对应的学生：\n",
      "语文    小郑\n",
      "数学    小郑\n",
      "dtype: str\n"
     ]
    }
   ],
   "source": [
    "scores = pd.DataFrame(\n",
    "    {\n",
    "        \"语文\": [80, 75, 92],\n",
    "        \"数学\": [85, 78, 88]\n",
    "    },\n",
    "    index=[\"小林\", \"小周\", \"小郑\"]\n",
    ")\n",
    "\n",
    "print(\"各列总分：\")\n",
    "print(scores.sum())\n",
    "print(\"各列平均分：\")\n",
    "print(scores.mean())\n",
    "print(\"每位学生两科总分：\")\n",
    "print(scores.sum(axis=1))\n",
    "print(\"各科最高分对应的学生：\")\n",
    "print(scores.idxmax())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0c0f2219",
   "metadata": {},
   "source": [
    "### 代码解读（对应上方示例）\n",
    "\n",
    "- 不写 `axis` 时，`scores.sum()` 和 `scores.mean()` 为每一列统计。\n",
    "- `scores.sum(axis=1)` 跨两列相加，为每个学生得到两科总分。\n",
    "- `scores.idxmax()` 返回每一科最高分所在的行标签，而不是最高分数本身。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6055edc9",
   "metadata": {},
   "source": [
    "### 动手练习"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "c730da12",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-30T13:01:33.949483Z",
     "iopub.status.busy": "2026-07-30T13:01:33.949355Z",
     "iopub.status.idle": "2026-07-30T13:01:33.951411Z",
     "shell.execute_reply": "2026-07-30T13:01:33.951044Z"
    }
   },
   "outputs": [],
   "source": [
    "# 计算每个城市两个月的总销量，并找出每个月销量最高的城市\n",
    "# sales = pd.DataFrame(\n",
    "#     {\"一月\": [80, 120, 100], \"二月\": [90, 110, 130]},\n",
    "#     index=[\"A\", \"B\", \"C\"]\n",
    "# )\n",
    "# city_totals = __________\n",
    "# monthly_best = __________\n",
    "# print(city_totals)\n",
    "# print(monthly_best)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0a16d8cc",
   "metadata": {},
   "source": [
    "## 16. 用 describe 快速生成数据概览\n",
    "\n",
    "`.describe()` 一次生成多项描述性统计。\n",
    "\n",
    "对数值列，它通常报告数量、平均值、标准差、最小值、四分位数和最大值；对文本列，它报告数量、不同值个数、最常见值及其频次。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9853ad20",
   "metadata": {},
   "source": [
    "### 示例代码"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "3e747db7",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-30T13:01:33.952878Z",
     "iopub.status.busy": "2026-07-30T13:01:33.952774Z",
     "iopub.status.idle": "2026-07-30T13:01:33.960243Z",
     "shell.execute_reply": "2026-07-30T13:01:33.959710Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "数值列概览：\n",
      "count     5.000000\n",
      "mean     80.400000\n",
      "std       8.792042\n",
      "min      68.000000\n",
      "25%      76.000000\n",
      "50%      82.000000\n",
      "75%      85.000000\n",
      "max      91.000000\n",
      "Name: 成绩, dtype: float64\n",
      "文本列概览：\n",
      "count      5\n",
      "unique     3\n",
      "top       数据\n",
      "freq       3\n",
      "Name: 方向, dtype: object\n"
     ]
    }
   ],
   "source": [
    "students = pd.DataFrame({\n",
    "    \"方向\": [\"数据\", \"金融\", \"数据\", \"营销\", \"数据\"],\n",
    "    \"成绩\": [82, 76, 91, 68, 85]\n",
    "})\n",
    "\n",
    "print(\"数值列概览：\")\n",
    "print(students[\"成绩\"].describe())\n",
    "print(\"文本列概览：\")\n",
    "print(students[\"方向\"].describe())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c995bb8e",
   "metadata": {},
   "source": [
    "### 代码解读（对应上方示例）\n",
    "\n",
    "- 成绩列是数值，`describe()` 给出均值、标准差和分位数等统计量。\n",
    "- 方向列是文本，结果中的 `unique` 是不同值个数，`top` 是最常见值，`freq` 是其出现次数。\n",
    "- `describe()` 适合快速检查数据，但具体问题仍要选择合适的单项统计方法。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0df879a1",
   "metadata": {},
   "source": [
    "### 动手练习"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "7eec8dd8",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-30T13:01:33.961852Z",
     "iopub.status.busy": "2026-07-30T13:01:33.961738Z",
     "iopub.status.idle": "2026-07-30T13:01:33.963578Z",
     "shell.execute_reply": "2026-07-30T13:01:33.963135Z"
    }
   },
   "outputs": [],
   "source": [
    "# 分别查看下表“销量”列和“城市”列的数据概览\n",
    "# sales = pd.DataFrame({\n",
    "#     \"城市\": [\"A\", \"B\", \"A\", \"C\"], \"销量\": [80, 120, 100, 95]\n",
    "# })\n",
    "# print(__________)\n",
    "# print(__________)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4864e2d0",
   "metadata": {},
   "source": [
    "## 17. 分类查看：unique 与 value_counts\n",
    "\n",
    "对 Series 使用：\n",
    "\n",
    "- `.unique()`：按首次出现顺序返回不重复的值；\n",
    "- `.value_counts()`：统计每个值出现的次数，默认按次数从多到少排列。\n",
    "\n",
    "它们常用于检查分类列的取值和分布。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4dc2780b",
   "metadata": {},
   "source": [
    "### 示例代码"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "d3d39361",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-30T13:01:33.965747Z",
     "iopub.status.busy": "2026-07-30T13:01:33.965606Z",
     "iopub.status.idle": "2026-07-30T13:01:33.969439Z",
     "shell.execute_reply": "2026-07-30T13:01:33.968845Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "不重复的方向：\n",
      "<ArrowStringArray>\n",
      "['数据', '金融', '营销']\n",
      "Length: 3, dtype: str\n",
      "各方向出现次数：\n",
      "数据    3\n",
      "金融    2\n",
      "营销    1\n",
      "Name: count, dtype: int64\n"
     ]
    }
   ],
   "source": [
    "directions = pd.Series([\"数据\", \"金融\", \"数据\", \"营销\", \"数据\", \"金融\"])\n",
    "\n",
    "print(\"不重复的方向：\")\n",
    "print(directions.unique())\n",
    "print(\"各方向出现次数：\")\n",
    "print(directions.value_counts())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a0cef2de",
   "metadata": {},
   "source": [
    "### 代码解读（对应上方示例）\n",
    "\n",
    "- `unique()` 返回“数据、金融、营销”，每个值只保留一次。\n",
    "- `value_counts()` 得到数据 3 次、金融 2 次、营销 1 次。\n",
    "- `unique()` 关注有哪些值；`value_counts()` 还回答每个值出现多少次。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "44eedd8f",
   "metadata": {},
   "source": [
    "### 动手练习"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "id": "95de43ef",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-30T13:01:33.971142Z",
     "iopub.status.busy": "2026-07-30T13:01:33.971038Z",
     "iopub.status.idle": "2026-07-30T13:01:33.973301Z",
     "shell.execute_reply": "2026-07-30T13:01:33.972560Z"
    }
   },
   "outputs": [],
   "source": [
    "# 查看城市列有哪些不同值，并统计各城市出现次数\n",
    "# cities = pd.Series([\"杭州\", \"宁波\", \"杭州\", \"温州\", \"宁波\", \"杭州\"])\n",
    "# unique_cities = __________\n",
    "# counts = __________\n",
    "# print(unique_cities)\n",
    "# print(counts)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8eb02829",
   "metadata": {},
   "source": [
    "## 教材中的阅读拓展\n",
    "\n",
    "以下内容在 `da_5f_pandas.pdf` 中有介绍，但不列入本次必须掌握范围：\n",
    "\n",
    "- **重建索引 `reindex`**：按新的行列标签重新排列并引入缺失值；\n",
    "- **索引对象集合运算**：交集、并集、差集及重复索引；\n",
    "- **DataFrame 与 Series 的复杂广播**：根据指定轴进行标签匹配；\n",
    "- **`apply` 与自定义函数映射**：需要先熟练掌握函数参数和返回值；\n",
    "- **`rank` 排名细节**：包括并列值的多种处理方法；\n",
    "- **相关系数与协方差**：`corr`、`cov`、`corrwith`；\n",
    "- **层次化索引**：属于后续多层索引专题。\n",
    "\n",
    "当前阶段最重要的是明确“行标签、列标签、整数位置、布尔条件”各自对应的选择方法，并坚持使用单次 `loc` 完成条件赋值。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "858bbc45",
   "metadata": {},
   "source": [
    "## 离场检验\n",
    "\n",
    "1. `df[\"成绩\"]` 与 `df[[\"成绩\"]]` 的结果类型有什么区别？\n",
    "2. `loc` 与 `iloc` 分别按什么选择数据？\n",
    "3. `df.loc[\"A\":\"C\"]` 是否包含标签 C？\n",
    "4. 多个 pandas 条件为什么使用 `&`，且每个条件要加括号？\n",
    "5. `isin([\"数据\", \"金融\"])` 检查的是什么？\n",
    "6. 怎样在成绩小于 60 的行中安全修改“是否及格”列？\n",
    "7. `sum(axis=1)` 对 DataFrame 得到的是每行还是每列的结果？\n",
    "\n",
    "<details>\n",
    "<summary>点击查看参考答案</summary>\n",
    "\n",
    "1. 前者是一维 Series，后者是只有一列的二维 DataFrame。\n",
    "2. `loc` 按标签，`iloc` 按从 0 开始的整数位置。\n",
    "3. 包含；`loc` 的标签切片包含末端。\n",
    "4. `&` 逐元素组合两个布尔 Series；括号保证每个比较条件先完成。\n",
    "5. 检查每个元素是否等于给定列表中的任意一个值。\n",
    "6. 使用 `df.loc[df[\"成绩\"] < 60, \"是否及格\"] = \"否\"`。\n",
    "7. 每行的结果。\n",
    "\n",
    "</details>"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (skills)",
   "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.14.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
