<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh_CN">
  <title>Luquiesceneの小屋</title>
  <subtitle>Hello</subtitle>
  <link href="https://luq-blog.xyz/" rel="alternate" type="text/html"/>
  <link href="https://luq-blog.xyz/atom.xml" rel="self" type="application/atom+xml"/>
  <id>https://luq-blog.xyz/</id>
  <updated>2026-10-02T15:53:00.000Z</updated>
  <entry>
    <title>芝诺佯谬｜无限不等于无穷大</title>
    <link href="https://luq-blog.xyz/posts/2026-10-02-zeno-paradoxe/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-10-02-zeno-paradoxe/</id>
    <published>2026-10-02T15:53:00.000Z</published>
    <updated>2026-10-02T15:53:00.000Z</updated>
    <summary>求普通时和芝诺时的变换关系式，并从数学层面拆解这个经典悖论。</summary>
    <content type="html"><![CDATA[<h2>背景介绍</h2>
<p>在古希腊的朴素认知中，运动是直观、真实、连续的自然现象，所有人都默认物体可以顺畅地从一点移动到另一点。但芝诺构造出了违背现实直觉的结论，其中最著名的就是阿喀琉斯追龟悖论。</p>
<p>悖论的核心设定非常简单：擅长奔跑的英雄阿喀琉斯，速度远快于乌龟，但若让乌龟提前出发一段距离，阿喀琉斯将永远无法追上乌龟。</p>
<p>芝诺的逻辑链条如下：</p>
<ul>
<li>阿喀琉斯想要追上乌龟，必须先抵达乌龟此刻所在的位置；</li>
<li>可当他跑到该位置时，乌龟已经向前爬出了新的距离；</li>
<li>当阿喀琉斯再次追到新位置，乌龟又再次前进。如此循环往复，追赶过程可以拆分为无穷多个步骤。</li>
</ul>
<p>在古人的认知局限里：无穷多个过程，必然需要无穷长的时间才能完成。因此芝诺得出结论：阿喀琉斯永远追不上乌龟，运动本身是虚假、不成立的。</p>
<p>这个悖论困扰了人类近两千年，直到微积分与收敛无穷级数诞生，人们才终于从数学层面彻底破解了这个千年佯谬，厘清了芝诺的核心逻辑漏洞。</p>
<h2>模型构建</h2>
<p>我们可以先求普通时$t$和芝诺时$t'$的变换关系。</p>
<p>设开始时两者相距$L$，两者速度分别为$v_1, v_2$（$v_1&gt;v_2$）。</p>
<p>在普通时间体系中，追上的总时间：
$$
t=\frac{L}{v_1-v_2}
$$</p>
<p>芝诺时$t'$的定义：<strong>$t'=n$，代表后一者到达前一者在$t'=n-1$时刻所在的位置</strong>。</p>
<ul>
<li>$t'=0,\quad t=0$</li>
<li>$t'=1,\quad t=\frac{L}{v_1},\quad L_1=\frac{L}{v_1}\cdot v_2$</li>
<li>$t'=2,\quad t=\frac{L}{v_1}+\frac{L}{v_1}\cdot\frac{v_2}{v_1},\quad L_2=\frac{L}{v_1}\cdot\frac{v_2}{v_1}\cdot v_2$</li>
<li>$t'=3,\quad t=\frac{L}{v_1}+\frac{L}{v_1}\cdot\frac{v_2}{v_1}+\frac{L}{v_1}\cdot\left(\frac{v_2}{v_1}\right)^2,\quad L_3=\dots$</li>
</ul>
<p>一般地，当$t'=n$时：
$$
t=\frac{L}{v_1}+\frac{L}{v_1}\cdot\frac{v_2}{v_1}+\frac{L}{v_1}\cdot\left(\frac{v_2}{v_1}\right)^2+\dots+\frac{L}{v_1}\cdot\left(\frac{v_2}{v_1}\right)^{n-1}
$$</p>
<p>等比数列求和公式：
$$
S_n=a_1\cdot\frac{1-q^n}{1-q}
$$
$a_1$为首项，$q$为公比，$n$为项数。</p>
<p>代入进行推导：
$$
\begin{aligned}
t&amp;=\frac{L}{v_1}\cdot\left[1+\frac{v_2}{v_1}+\left(\frac{v_2}{v_1}\right)^2+\dots+\left(\frac{v_2}{v_1}\right)^{n-1}\right] \
&amp;=\frac{L}{v_1}\cdot\frac{1-\left(\frac{v_2}{v_1}\right)^n}{1-\frac{v_2}{v_1}} \
&amp;=\frac{L}{v_1-v_2}\cdot\left[1-\left(\frac{v_2}{v_1}\right)^n\right]
\end{aligned}
$$</p>
<p>将$n$替换为芝诺时$t'$，得到变换关系：
$$
\boldsymbol{t=\frac{L}{v_1-v_2}\cdot\left[1-\left(\frac{v_2}{v_1}\right)^{t'}\right]}
$$</p>
<p>当$t'\to\infty$时：
$$
t=\frac{L}{v_1-v_2}
$$</p>
<hr />
<h2>佯谬的本质</h2>
<p>用一句话概括：无限分割 ≠ 无限长的时间</p>
<p>芝诺混淆了<strong>时间可以无限细分</strong>和<strong>时间总量无限</strong>这两个概念。每一次“后者跑到前者上一轮所在的位置”，算作芝诺时的1个单位。在这套计时规则下，想要完成“追上”这件事，必须经过无穷多个阶段，也就是$t' \to \infty$。</p>
<p>但这里的关键是：<strong>无穷多个阶段，对应的真实物理时间总和是有限的</strong>。我们上面推导的等比级数是收敛级数，无穷多项相加，最终结果是一个有限数值。通俗来说，芝诺只是把一段<strong>有限长度的时间</strong>，切割成了无穷多小段。完成无穷多个步骤，并不意味着需要无穷久的时间。因为数学的无穷级数告诉我们，有限的时间区间，完全可以拆分为无穷多段更小的时间片段。</p>
<p>芝诺时$t'$趋向无穷，仅仅代表分割的次数无穷，并不代表真实物理时间$t$会变成无穷大。</p>
<hr />
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="物理学"/>
  </entry>
  <entry>
    <title>物理竞赛｜复习公式</title>
    <link href="https://luq-blog.xyz/posts/2026-09-17-physics-formulas/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-09-17-physics-formulas/</id>
    <published>2026-09-17T00:00:00.000Z</published>
    <updated>2026-10-01T00:00:00.000Z</updated>
    <summary>物理竞赛复赛备考整理，力学、热学、光学、近代物理、数学工具与实验不确定度公式汇总。</summary>
    <content type="html"><![CDATA[<h2>一、力学</h2>
<h3>1. 运动学</h3>
<p>角速率为角位置对时间的一阶导数，角加速度为角速度对时间的一阶导数：
$$
\dot{\theta}=\omega \quad , \dot{\omega}=\beta
$$</p>
<p>转动参考系中质点加速度，包含向心加速度与切向加速度两项：
$$
\vec{a}=\vec{\omega}\times\vec{v}+\vec{\beta}\times\vec{r}
$$</p>
<p>极坐标系下速度分解公式，$\hat{r}$ 径向、$\hat{\theta}$横向单位矢量：
$$
\vec{v}=\dot{r}\hat{r}+r\dot{\theta}\hat{\theta}
$$</p>
<p>极坐标系下加速度分解公式：
$$
\vec{a}=(\ddot{r}-r\dot{\theta}^2)\hat{r}+(r\ddot{\theta}+2\dot{r}\dot{\theta})\hat{\theta}
$$</p>
<p>科里奥利力（科氏力），$\vec{\omega}$ 为参考系角速度，$\vec{v}'$ 为质点相对参考系的相对速度：
$$
\vec{F}=-2m\vec{\omega}\times\vec{v}'
$$</p>
<h3>2. 转动</h3>
<p>力矩定义式；力矩等于角动量对时间的变化率；刚体转动定律：
$$
\vec{M}=\vec{r}\times\vec{F} \quad \vec{M}=\frac{d\vec{L}}{dt} \quad M=I\beta
$$</p>
<p>角动量定义式；刚体绕定轴角动量：
$$
\vec{L}=\vec{r}\times\vec{p} \quad L=I\omega
$$</p>
<p>转动惯量定义（离散质点系）：
$$
I=\sum m_i r_i^2
$$</p>
<p>平行轴定理：刚体对任意轴转动惯量 = 对过质心平行轴转动惯量 + 质心到转轴距离相关项
$$
I_O=I_c+md^2
$$</p>
<p>垂直轴定理，适用于二维薄平面刚体：
$$
I_z=I_x+I_y
$$</p>
<blockquote>
<p>常用转动惯量公式</p>
</blockquote>
<table>
<thead>
<tr>
<th>物体</th>
<th>转轴</th>
<th>公式</th>
</tr>
</thead>
<tbody>
<tr>
<td>细棒</td>
<td>过中心垂直</td>
<td>$$I=\frac{1}{12}ML^2$$</td>
</tr>
<tr>
<td>细棒</td>
<td>过端点垂直</td>
<td>$$I=\frac{1}{3}ML^2$$</td>
</tr>
<tr>
<td>圆环/薄圆柱环</td>
<td>过中心轴垂直于面</td>
<td>$$I=MR^2$$</td>
</tr>
<tr>
<td>实心圆盘</td>
<td>过中心轴垂直于面</td>
<td>$$I=\frac12MR^2$$</td>
</tr>
<tr>
<td>细圆环</td>
<td>直径</td>
<td>$$I=\frac12MR^2$$</td>
</tr>
<tr>
<td>实心球</td>
<td>中心线</td>
<td>$$I=\frac25MR^2$$</td>
</tr>
<tr>
<td>空心球壳</td>
<td>中心线</td>
<td>$$I=\frac23MR^2$$</td>
</tr>
</tbody>
</table>
<h3>3. 能量</h3>
<p>刚体转动动能公式：
$$
E_k=\frac12I\omega^2
$$</p>
<p>万有引力引力势能：
$$
E_p=-G\frac{Mm}{r}
$$</p>
<p>天体椭圆轨道总机械能（动能+引力势能，$a$为椭圆半长轴）：
$$
E=-G\frac{Mm}{2a}
$$</p>
<p>柯尼希定理：系统总动能 = 质心动能 + 相对质心运动动能
$$
E_k=E_{k相对}+E_c
$$</p>
<h3>4. 简谐振动</h3>
<p>简谐回复力；角频率；周期公式：
$$
F=-kx \quad , \omega=\sqrt{\frac{k}{m}},\quad T=\frac{2\pi}{\omega}
$$</p>
<p>简谐振动位移表达式，$A$振幅，$\varphi$初相位：
$$
x = A\cos(\omega t+\varphi)
$$</p>
<p>简谐振动微分方程：
$$
\ddot{x}+\frac{k}{m}x=0
$$</p>
<p>简谐振动标准微分形式，解为三角函数振荡解：
$$
\ddot{x}+\omega^2x=0
$$</p>
<p>指数型微分方程，解为实指数（非振荡）：
$$
\ddot{x}-\omega^2x=0
$$</p>
<hr />
<h2>二、热学</h2>
<p>理想气体状态方程：
$$
pV=nRT
$$</p>
<p>理想气体压强微观表达式，式中$n$代表分子数密度：
$$
p=nkT
$$</p>
<p>理想气体内能公式，单原子分子$i=3$，刚性双原子分子$i=5$：
$$
E=n\frac{i}{2}RT
$$</p>
<p>定容摩尔热容；迈耶公式（定压摩尔热容）：
$$
C_V=\frac{i}{2}R \quad C_p=C_V+R
$$</p>
<p>绝热指数（比热容比）：
$$
\gamma=\frac{C_p}{C_V}
$$</p>
<p>多方过程通用过程方程：
$$
pV^n=\text{Const}
$$
$n=0$：等压 $\quad n=1$：等温 $\quad n=\gamma$：绝热 $\quad n\to\infty$：等容</p>
<p>热力学第一定律，$Q$系统吸热，$W$外界对系统做功：
$$
\Delta U=Q+W
$$</p>
<p>卡诺热机效率，$T_1$高温热源温度，$T_2$低温热源温度：
$$
\eta=1-\frac{T_2}{T_1}
$$</p>
<hr />
<h2>三、光学</h2>
<h3>1.几何光学</h3>
<p>折射率定义：
$$
n=\frac{c}{v}
$$</p>
<p>折射定律（斯涅尔定律）：
$$
n_1\sin i_1 = n_2\sin i_2
$$</p>
<p>薄透镜成像公式：
$$
\frac1u+\frac1v=\frac1f
$$</p>
<p>横向放大率公式：
$$
m=-\frac{v}{u}
$$</p>
<p>单球面折射公式：
$$
\frac{n_1}{u}+\frac{n_2}{v}=\frac{n_2-n_1}{r}
$$</p>
<p>球面镜反射成像公式：
$$
\frac1u+\frac1v=\frac1f
$$</p>
<h3>2.波动光学</h3>
<p>光程定义：折射率乘以几何路程
$$
\Delta = nL
$$</p>
<hr />
<h2>四、近代物理</h2>
<h3>1.类氢原子</h3>
<p>库仑力提供圆周运动向心力：
$$
k\frac{Ze\cdot e}{r^2}=m\frac{v^2}{r}
$$</p>
<p>玻尔角动量量子化条件：
$$
mvr = n\hbar
$$</p>
<p>约化普朗克常数定义：
$$
\hbar=\frac{h}{2\pi}
$$</p>
<p>类氢原子能级（总能量，动能+势能）：
$$
E=E_k+E_p = -\frac{mk^2Z^2e^4}{2\hbar^2n^2}
$$</p>
<h3>2.相对论</h3>
<p>相对论能量动量关系：
$$
E^2=(m_0c^2)^2+(pc)^2
$$</p>
<hr />
<h2>五、通用数学</h2>
<p>泰勒级数，函数在$x=a$处展开：
$$
f(x)=\sum_{n=0}^{\infty}\frac{f^{(n)}(a)}{n!}(x-a)^n
$$</p>
<p>洛必达法则，0/0或∞/∞型极限，逐次求导：
$$
\lim_{x\to x_0} \frac{f(x)}{g(x)}=\frac{f'(x)}{g'(x)}=\frac{f''(x)}{g''(x)}=\dots
$$</p>
<p>分部积分公式：
$$
\int u,dv = uv-\int v,du
$$</p>
<p>乘积微分法则：
$$
d(uv)=u,dv+v,du
$$</p>
<p>分式微分法则：
$$
d\left(\frac{u}{v}\right)=\frac{v,du-u,dv}{v^2}
$$</p>
<hr />
<h2>六、实验</h2>
<h3>1.不确定度</h3>
<p>A类不确定度（多次测量统计不确定度），共$n$组测量数据：
$$
U_A=\sqrt{\frac{\sum_{i=1}^n(N_i-\bar{N})^2}{n(n-1)}}
$$</p>
<p>B类不确定度（正态分布）：
$$
U_B=\frac{\Delta x}{3}
$$</p>
<p>B类不确定度（均匀分布）：
$$
U_B=\frac{\Delta x}{\sqrt{3}}
$$</p>
<p>合成标准不确定度：
$$
U_C=\sqrt{U_A^2+U_B^2}
$$</p>
<blockquote>
<p>不确定度合成公式
若间接测量量$N,\ N=f(x,y,\dots)$，$x,y,\dots$为直接测量量：</p>
</blockquote>
<table>
<thead>
<tr>
<th>函数关系</th>
<th>不确定度传递公式</th>
</tr>
</thead>
<tbody>
<tr>
<td>$N=x\pm y$</td>
<td>$$U_{(N)}=\sqrt{U(x)^2+U(y)^2}$$</td>
</tr>
<tr>
<td>$N=xy \text{ 或 } N=\dfrac{x}{y}$</td>
<td>$$\dfrac{U_{(N)}}{\lvert N \rvert}=\sqrt{\left(\dfrac{U(x)}{x}\right)^2+\left(\dfrac{U(y)}{y}\right)^2}$$</td>
</tr>
<tr>
<td>$N=kx\ (k=\text{Const})$</td>
<td>$$U(N)=\lvert k \rvert\ U(x)$$</td>
</tr>
<tr>
<td>$N=x^k\ (k=\text{Const})$</td>
<td>$$\dfrac{U(N)}{\lvert N \rvert}=\lvert k \rvert\dfrac{U(x)}{x}$$</td>
</tr>
</tbody>
</table>
<h3>2.最小二乘法</h3>
<p>一元线性拟合函数 $y = b_1 x + b_0$，斜率$b_1$、截距$b_0$满足方程组：
$$
\begin{cases}
b_1=\dfrac{\overline{xy}-\bar{x}\cdot\bar{y}}{\overline{x^2}-(\bar{x})^2}\[6pt]
b_0=\bar{y}-b_1\bar{x}
\end{cases}
$$</p>
<hr />
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="物理学"/>
  </entry>
  <entry>
    <title>国际夏令营｜全记录</title>
    <link href="https://luq-blog.xyz/posts/2026-07-19-sco-ysc-diary-01/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-07-19-sco-ysc-diary-01/</id>
    <published>2026-07-19T00:00:00.000Z</published>
    <updated>2026-07-20T00:00:00.000Z</updated>
    <summary>记录在SCO-YSC国际夏令营的一些经历</summary>
    <content type="html"><![CDATA[<h1>1. 前言</h1>
<p>开始写这篇文章的时间是当地时间（UTC +6）23：55。我们将在第二天当地时间 2：00 启程回国。在此之前，我们进行了短暂的离别，或许不是最后的离别，因为出发前他们可能还要送别。确实有点不舍啊o(TヘTo)</p>
<p>在此之前，我几乎每天都在写日记。不过我觉得那些长（liu）文（shui）章（zhang）无法完整地叙述这段经历，所以我写一篇总集，来回顾这些天发生的事情。</p>
<p>:::important[一个必要的声明]
如此文章内容与官方媒体报道内容有偏差，请以官方媒体内容为准。
:::</p>
<p>鉴于事情实在有些庞杂，我尝试用<strong>专题 + Q&amp;A</strong>的形式来写这篇文章。你可以使用<code>目录</code>来跳转相关内容。</p>
<p>希望能在这段还能联网的时间快点结束这篇文章吧。</p>
<hr />
<h1>2. 此项目的背景</h1>
<h2>Q1：参加的是什么项目？</h2>
<p>A1：项目名称是 <strong>SCO Young Scientists Camp</strong>（上海合作组织青年科学家营）。</p>
<p>看名称你就知道了，其实这不是一个完全的科研项目，也不是一个传统意义上的国际夏令营。这应该只是一个由官方举办的科研向国际夏令营而已。在这里我们确实做了一些研究，并且团队协作写了自己的论文。不过和真正的科学家相比，我们都还有很大的差距。</p>
<h2>Q2：在什么地方？</h2>
<p>A2：我们的总部在吉尔吉斯斯坦（吉尔吉斯共和国）的乔尔蓬阿塔市、位于伊塞克湖畔的阿克曼总统学校。</p>
<p>这里风景非常美丽。由于靠近伊塞克湖，温度也比较适宜（比新疆凉快一些(°°)～）。</p>
<p>不过我们并不是一直呆在这里，期间我们还有 2 天出去旅行，乘坐中型客车沿着伊塞克湖绕行。下文会说到这部分。</p>
<h2>Q3：项目持续的时间？</h2>
<p>A3：根据官方通知，算上途中的时间，应当为 <strong>2026 年 7 月 5 日至 2026 年 7 月 20 日</strong>。</p>
<p>不过嘛，由于 7 月 5 日我们的签证还没有办下来，实际上我们是 7 月 6 日早上才出发的。而且由于航班的数量有限，回程航班的时间实际上是当地时间 7 月 20 日 9：00 左右。</p>
<h2>Q4：来回花了多少钱？</h2>
<p>A4：往返机票、食宿费、参加项目所需的经费都是学校报销！不过我们买纪念品和其他零食的费用是需要自己付的。</p>
<p>我到这地方总共连 100 美元都没有花完，剩下的钱都兑换回去了。至于出发前的一系列准备花费的钱嘛...点击此处揭晓答案：:spoiler[保密哦~哈哈(‾◡◝)]！</p>
<h2>Q5：我是怎么得到这个机会的？</h2>
<p>:::note[站长注]
以后这种不知道把Ax写在哪里的情况，我就不写Ax了（
:::</p>
<p>说来也巧，就在很久很久以前...<s>走错片场了吧啊喂！</s></p>
<p>啊，也不是很久啦。大概在 5 月的时候，老师就告诉了我这件事。我莫名其妙就成了提名候选人。然后呢，文件说要在中国的 15 个提名候选人中选出 10 个人参加这个项目。选拔方式也很简单，就是写一个文书。没错，只是写文书。</p>
<p>至于最后的结果，显而易见了吧...</p>
<p>但这并不是真相！实际上，这 15 个人最终都参加了。你问我为什么！？我也不知道为什么，哈哈O_O</p>
<p><s>不过既然我这种人都能来，想必咱们就是个草台班子吧（对不起</s></p>
<p>至于期末考试嘛...哈哈，其实我早在那个时候就已经知道自己大概率参加不了本学期的期末考试了。不过我其实一直都有在和平常一样<s>不怎么</s>认真的学习哦，应该吧。</p>
<h2>Q6：出发前有什么有趣的事？</h2>
<p>事情是这样的：</p>
<h3>所以这活动还搞不搞了？</h3>
<p>本来通知的是 5 月 31 日返回最终候选人名单。但是，我一直等到 6 月都没有消息。我一度以为我已经与这个项目无缘了，其实已经做好了正常参加期末考试的准备。没想到！我居然会在即将出发的时候收到通知！</p>
<p>没错，就是在几乎是出发前两周的时间，我才收到通知。这就意味着，我们必须立即去办护照和签证，也不能保证 7 月 5 日一定可以出发。</p>
<p>嗯就是这样。</p>
<h3>写作业 or 不写作业？玩的就是心跳</h3>
<p>我们原本定在 7 月 5 日出发，也就是学考三天之后的休息日的第二天，即 7 月 6 日就要正常上课了。可是这段时间我们一直在等待签证，即使到了 7 月 5 日的下午，我们也没有收到任何关于订机票的通知。这时候，我的作业还一个字没写啊！！！</p>
<p>所以那个下午，我绝望地...写完了 5 天中的大部分作业〒▽〒</p>
<p>然后，当天晚上就发出了第二天出发的通知。</p>
<hr />
<h1>3. 初来乍到，请多多指教</h1>
<h2>Q7：我是怎么到这地方的？</h2>
<p>A7：请看下面这篇文章：</p>
<p>&lt;a href="/posts/2026-07-06-sco-ysc-dairy-02/"&gt;日常｜SCO-YSC日记02｜出发&lt;/a&gt;</p>
<h2>Q8：第一天到这里，怎么样啊？</h2>
<p>A8：请看下面这篇文章：</p>
<p>&lt;a href="/posts/2026-07-07-sco-ysc-dairy-03/"&gt;日常｜SCO-YSC日记03｜我好像穿越了（bushi&lt;/a&gt;</p>
<h2>Q9：这里上网冲浪怎么样？</h2>
<h3>国内可以用的，这里用不了</h3>
<p>比如说哔哩哔哩。可惜了我之前缓存的 2 部番啊！一旦出国，因为版权限制，即便缓存了动漫，也不能看。不过我有替代选项，那就是使用 lanerc。（这一段好像在前文提到过，那就不放照片了吧）</p>
<h3>国内不能用的，这里可以用</h3>
<p>比如说，这里的 GitHub 居然可以直连！</p>
<p>还可以用 Telegram，一个社交软件，类似于微信。我和组内的人员联系，都用这个软件。</p>
<hr />
<h1>4. 在这里的科学研究</h1>
<h2>Q10：我选择的方向是？</h2>
<p>A10：天文数据科学！！！（超大声</p>
<p>英文名是 <strong>Astro-data Science</strong> 。天文学的实验室可以说几乎是摆设了，因为天文学就没法做实验啊！( •̀ ω •́ )y 我们大部分时间都在跑代码，而且涉及的理论知识比较多，凭个人感觉，这恐怕是整个项目里最累的方向了吧...</p>
<p>不过，我并不后悔选这个方向，因为天文学和计算机科学本来就是我十分喜欢的学科啊，能有这个机会真是太幸运啦！(≧∀≦)ゞ</p>
<h2>Q11：到这里还要上课嘛？</h2>
<p>A11：确实上课了，但不多。</p>
<p>emmm...其实真正的天文数据分析专业课只上了 4 节，一天中的相当大部分时间都是自由的。有时候我们早上会有大课，讲的是怎么写论文和做海报。但是这种大课我一般都不听，因为我们队的进度始终先于讲课的内容，而且队长已经帮我们写好大部分的论文了！</p>
<h2>Q12：那我都在做什么啊？</h2>
<p>A12：事情多了，有种比在学校上课还累的感觉。</p>
<p>:::note[站长注]
个人认为，根据每个方向的不同，甚至是根据在团队中担任角色的不同，在这里的工作强度都是不一样的
:::</p>
<p>如果上午上了专业课，下午和晚上我就要复习和分析课上讲的代码。这不是必须完成的作业，只是因为我不想看似什么都学了，实际上什么都没学到啊。如果这样的话，我的技术应该一直都不会提升太多。</p>
<p>有时候也会读论文。我们会先用谷歌学术搜索找到和自己的研究内容相关的论文，然后就是专注地读论文。纯英文的论文真的非常难读，哪怕使用了翻译软件。里面有大量的天文学和计算机科学的专业术语。这种论文我一共读了 4 篇。有没有用上就另当别论了，哈哈(￣▽￣)"</p>
<p>这里我是不是应该放剩下的代码分析啊...</p>
<p>&lt;a href="/posts/2026-07-08-astro-data-science-02-period-detective/"&gt;笔记｜天文数据科学02｜周期侦探&lt;/a&gt;</p>
<p>&lt;a href="/posts/2026-07-09-astro-data-science-03-machine-learning-classifier/"&gt;笔记｜天文数据科学03｜机器学习甄别系外行星&lt;/a&gt;</p>
<p>剩下一个是 CNN 卷积的，我还没做。</p>
<h2>Q13：我们团队的工作流程是什么？</h2>
<p>A13：篇幅有限，我不能完全讲明白，所以这里简略说明。</p>
<p>首先，队长给我提供 50 个候选行星的 TESS ID，以及一些相关数据。</p>
<p>然后，我需要使用 BLS （Box Least Squares）算法，找出这些候选行星的凌日现象和凌日周期；使用 Block Bootstrap 算法，估算误差；使用奇偶深度校验（odd-even transit vetting），消除食双星（eclipsing binary），再结合这些测试的结果，找出比较符合行星特征的候选行星。这些数据将交给下一个负责机器学习的人。</p>
<p>我知道这些样本不够啊，所以我们后面还做了 300 个数据的补充。</p>
<p>机器学习那边，需要使用随机森林模型（RF）和 CNN 卷积，训练 2 个不同的模型。</p>
<p>啊啊啊我这到底说了个什么啊。感觉好混乱o((&gt;ω&lt; ))o</p>
<hr />
<h1>5. 一些补充</h1>
<p>我想想，现在好像没太多时间完善这个文章了，要不你到我每天的随笔里找？但不保证质量啊(∪.∪ )...zzz</p>
<p>&lt;a href="/posts/2026-07-08-sco-ysc-diary-04/"&gt;日常｜SCO-YSC日记04｜第二天的见闻&lt;/a&gt;</p>
<p>&lt;a href="/posts/2026-07-09-sco-ysc-diary-05/"&gt;日常｜SCO-YSC日记05｜逐渐适应中。。。&lt;/a&gt;</p>
<p>&lt;a href="/posts/2026-07-10-sco-ysc-diary-06/"&gt;日常｜SCO-YSC日记06｜外出旅行&lt;/a&gt;</p>
<p>&lt;a href="/posts/2026-07-11-sco-ysc-diary-07/"&gt;日常｜SCO-YSC日记07｜活着真好啊&lt;/a&gt;</p>
<p>&lt;a href="/posts/2026-07-12-sco-ysc-diary-08/"&gt;日常｜SCO-YSC日记08｜疯狂推进度的一天&lt;/a&gt;</p>
<p>&lt;a href="/posts/2026-07-13-sco-ysc-diary-09/"&gt;日常｜SCO-YSC日记09｜所以工作的意义是？&lt;/a&gt;</p>
<p>&lt;a href="/posts/2026-07-14-sco-ysc-diary-10/"&gt;日常｜SCO-YSC日记10｜又是跑代码的一天啊&lt;/a&gt;</p>
<p>&lt;a href="/posts/2026-07-15-sco-ysc-diary-11/"&gt;日常｜SCO-YSC日记11｜论文写完了，终于&lt;/a&gt;</p>
<p>&lt;a href="/posts/2026-07-18-sco-ysc-diary-12/"&gt;日常｜SCO-YSC日记12｜又要交这个东西了...&lt;/a&gt;</p>
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="思录"/>
  </entry>
  <entry>
    <title>国际夏令营｜又要交这个东西了...</title>
    <link href="https://luq-blog.xyz/posts/2026-07-18-sco-ysc-diary-12/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-07-18-sco-ysc-diary-12/</id>
    <published>2026-07-18T00:00:00.000Z</published>
    <updated>2026-07-18T00:00:00.000Z</updated>
    <summary>好</summary>
    <content type="html"><![CDATA[<h1>1. 前言</h1>
<p>今天要交随笔，所以我写一个今天的随笔。</p>
<p>TODO: 将此文章合并到合集中。加入照片。</p>
<h1>2. 我好像过了一个上午</h1>
<p>今天上午是海滩（确切地说，是湖滩）活动，对我来说当然就是休息了！</p>
<p>所以，其实我根本没到那里去。吃完早饭后，我就在宿舍里睡觉。然后一直睡了一个上午，哈哈。</p>
<h1>3. 到湖里去！</h1>
<p>按照计划，下午我们要坐船到湖里玩。但是！当然不是从学校坐船，因为这里根本没有港口。所以我们先坐了一段时间的大型客车。</p>
<p>一路上，警察都在给我们开路。我刚好坐在第一排，目睹了警车带我们在拥堵的车流中穿行的全过程。</p>
<p>然后我们到船上。呆了一会儿，回来了。</p>
<h1>3. 国际之夜</h1>
<p>排练了这么多天，我们终于可以去表演了。在这个活动中，吉尔吉斯斯坦、中国、俄罗斯、塔吉克斯坦、乌兹别克斯坦 5 个国家的人先后表演了节目。</p>
<p>活动结束后，我收到了一本纪念册。我发现大合照中我就站在第一排的正中间，好幸运啊。</p>
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="思录"/>
  </entry>
  <entry>
    <title>国际夏令营｜论文写完了，终于</title>
    <link href="https://luq-blog.xyz/posts/2026-07-15-sco-ysc-diary-11/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-07-15-sco-ysc-diary-11/</id>
    <published>2026-07-15T00:00:00.000Z</published>
    <updated>2026-07-15T00:00:00.000Z</updated>
    <summary>啊哈哈哈哈哈哈哈哈哈哈哈哈哈...</summary>
    <content type="html"><![CDATA[<h1>1. 糟糕的上午</h1>
<p>好困啊，早上就根本不想起床(￣﹃￣)因为我昨天只睡了 6 个多小时。因为回去以后还得跑代码、发邮件。我好像一躺到床上，就睡着了，甚至忘记了洗澡这件事。然后，当我起来的时候，还是好困啊。</p>
<p>吃完早饭后，还是一节大课。这节课老师讲的是数据分析。一看到这节课的主题，我就意识到，这课就不用听了，因为...不然你觉得这几天我在干什么啊！所以我什么都没听进去，也没休息好。只是感觉上了一节课而已。</p>
<p>然后，最难受的一个上午就到来了。因为我昨天的 300 个数据是从 KEPLER 下载的，而我们的论文标题中有“in TESS Data”。这就意味着，我不得不把数据来源换成 TESS。这是十分耗时且困难的事情，因为 KEPLER 的数据支持多线程下载，而 TESS 的数据只能用 lightkurve 库一个一个下载（在我的认知中，是这样的，可能不准确）。</p>
<p>经过一上午的尝试和等待，我都快要崩溃了！数据死活下载不下来，而且我的电脑还黑屏了一次。</p>
<p>但令人欣慰的是，我们不必一定要从 TESS 获取数据，但代价是需要修改论文中的部分内容。所以我对我的搭档感到十分抱歉，因为这浪费了他们一上午的时间。幸运的是，我们的论文进度比较超前，所以当别人还在写论文时，我们就已经完成了。</p>
<h1>2. 不知所措的中午和下午</h1>
<p>我吃完午饭后，本来想回宿舍休息一会儿。但是躺了一会儿后，我突然意识到今天要开全体大会！！！那就意味着，计划泡汤了╯︿╰</p>
<p>大会开完后，我一点也不慌，因为会上只讲了关于我们成果的截止日期的事情，没有太多值得注意的事。不过，这时候也不能睡觉了。</p>
<p>之后的一段时间，我一直在工位上，但我几乎无事可做，因为我不用写论文中的任何东西，除了我的名字。因为我负责的东西都被队长写了！哈哈(✿◡‿◡)</p>
<p>所以我就出去转转吧。我去了天文实验室（这好像是我第二次进去吧，第一次是在参观的时候，哈哈）、海滩，看了看其他人的项目，然后去吃晚饭。虽然是晚饭，但我感觉这时候才是下午啊。</p>
<h1>3. 晚饭后</h1>
<p>重大事件！我们组居然有 5 个人！？w(ﾟДﾟ)w</p>
<p>由于那个人一点英语也不会，他没跟我们说过话，我一直都不知道他是我们组的，直到今天。我还以为他是这里的志愿者，哈哈...</p>
<p>我还了解到，我的同桌 S 同学（暂且这么称呼他吧）居然也看动漫！</p>
<p>一开始，他只是问我喜欢什么电影。我说我不看电影，看动画片。然后他给我看了一些动画片的照片。我说，我看的不是美国的动画片，而是日本的动画片。然后他说，可以给他展示一些照片吗？我的博客就这样被他发现了！之后，他又给我展示了他看过的动漫。他也看过《总之就是非常可爱》，还说我像男主 NASA，我真的，好开心啊，哈哈...</p>
<p><img src="1.webp" alt="" /></p>
<p>我本来还以为他们都不喜欢二次元的。虽然我们有着不同的语言，但并不妨碍我们有相同的爱好啊。</p>
<h1>4. 总结</h1>
<p>今天是论文的截止日期，所以总结很忙。请不要找他。</p>
<p><s>到底什么时候我才能写个真正的总结啊，哈哈</s></p>
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="思录"/>
  </entry>
  <entry>
    <title>国际夏令营｜又是跑代码的一天啊</title>
    <link href="https://luq-blog.xyz/posts/2026-07-14-sco-ysc-diary-10/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-07-14-sco-ysc-diary-10/</id>
    <published>2026-07-14T00:00:00.000Z</published>
    <updated>2026-07-14T00:00:00.000Z</updated>
    <summary>模型终于训练的差不多啦！</summary>
    <content type="html"><![CDATA[<p>我这次不写标题，想到什么写什么，因为太忙了。</p>
<p>我的电脑昨天晚上一整晚都没睡，500个样本都完成了。</p>
<p>我做了个噩梦，梦到早上看见代码报错，一整晚白运行了。然后我就醒了，看到全都完成了。</p>
<p>昨天去观察月亮，发现我看错日期了。明天才能去。今天的明天。</p>
<p>我天天发邮件，感觉一个学期应用文都在这几天写完了。</p>
<p>我上午刚把csv发给他们。</p>
<p>我们论文已经快写完了。但不是我写的。</p>
<p>现在上大课，但我不想听。因为讲的东西我们都弄完了，哈哈。</p>
<p>我在大课上用一些手段成功注册了telegram</p>
<p>模型没训练好，阳性样本太少了，所以我又花了很多时间去找。进度的快慢取决于程序的运行速度。</p>
<p>机器学习那边的代码一直报错，然后我用Excel打开，发现信噪比那一列，哈哈哈哈哈哈哈哈哈。居然被识别成了日期！</p>
<p>我们机器学习的数据基本上都过拟合。曲线及其不正常。我快 21 点才调好模型。明天还要写方法论。</p>
<p>写完之后，我发现不是每一句话都有“我”这个字，所以我把每一句话都加上了“我”这个字。（除了标题和简介。）</p>
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="思录"/>
  </entry>
  <entry>
    <title>国际夏令营｜所以工作的意义是？</title>
    <link href="https://luq-blog.xyz/posts/2026-07-13-sco-ysc-diary-09/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-07-13-sco-ysc-diary-09/</id>
    <published>2026-07-13T00:00:00.000Z</published>
    <updated>2026-07-13T00:00:00.000Z</updated>
    <summary>啊我怀疑我这些天的代码白写了。</summary>
    <content type="html"><![CDATA[<p>今天好累，但老师说必须要写，所以我就写了〒▽〒</p>
<h1>1. 热情地工作，然后就消极了</h1>
<p>今天上午和下午，我都在非常努力地在团队中做贡献。虽然我昨天已经完成了代码任务，但是我们组有很多人都觉得 50 个数据不足以满足机器学习的样本要求。所以，我今天又去寻找并分析了 500 个新的数据！！！</p>
<p>没错，我一天就要完成之前所有的任务总和，并将它们翻十倍。</p>
<p>不过啊，其实没有那么严重。因为我的代码已经全都写好了，只需要改文件路径就可以。至于剩下的，就交给计算机完成吧。</p>
<p>TODO: 以后补充更多内容（不摸的话）<s>就算不摸鱼，我天天这么忙，真的有时间吗O_O</s></p>
<p>再见。</p>
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="思录"/>
  </entry>
  <entry>
    <title>国际夏令营｜疯狂推进度的一天</title>
    <link href="https://luq-blog.xyz/posts/2026-07-12-sco-ysc-diary-08/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-07-12-sco-ysc-diary-08/</id>
    <published>2026-07-12T00:00:00.000Z</published>
    <updated>2026-07-12T00:00:00.000Z</updated>
    <summary>今天我终于完成代码任务了。</summary>
    <content type="html"><![CDATA[<h1>1. 一节奇怪的课程</h1>
<p>我今天终于完成我在团队中的代码任务了，哈哈。不过就是不知道以后会不会被拉过去给别人帮忙。</p>
<p>但是，实际上今天上午我们的进度完全没有推进。这是因为，早上我们去上了一节大课，是讲关于如何写论文的内容。这节课真的令我印象深刻。</p>
<p>顺便，我没有拍任何关于这节课的照片，因为总觉得上课的时候拍照片不是很礼貌。</p>
<p>这位老师一开始，居然让我们把左脚的鞋脱下来，扔到前面去！！！（是的，我十分确定我没有打错字。）这是为什么呢？我也不知道，因为我的英语不太好，谷歌翻译还没来得及打开，哈哈。</p>
<p>然后，这节奇怪的课程就开始了。老师居然是通过扔球的方式选择谁来回答问题的。虽然回答问题还好，但我好害怕被球砸到啊。</p>
<p>然后我们就莫名其妙的开始了对钢琴凳的研究。我们需要介绍这个钢琴凳，然后寻找现有的对钢琴凳的研究文献。不过，这并不是全部由我们中国队完成的，因为吉尔吉斯斯坦的 A 同学（为了保护个人信息，暂且这样称呼她吧）帮助了我们。文本信息全都是由她来搜集的，我们并没有做太多事情。真是太感谢她了。</p>
<h1>2. 团队研究进展</h1>
<p>正因为上午参加了以上所述的课程，再加上停电，我们上午完全没有进展。</p>
<p>所以，下午我就要努力工作了。社团已结束，我就回宿舍拿了我的鼠标和电脑支架，然后前往教室，去敲代码。</p>
<p>在今天结束之前，我成功完成了我负责部分的所有内容。除了代码注释的翻译，我想明天再做这件事。但同时，我又非常担心我们的工作可能无法在规定时间内完成，所以我就先把表格发给了队长，希望她可以理解吧。</p>
<p>不过，还有一件有意思的事情。当我复盘项目的时候，才发现我的截止日期应该是7月14日，我提前了 2 天就完成了。这就意味着，我们给机器学习留有充足的时间。也确实，我负责的奇偶深度校验、行星半径物理合理性校验老师还没有讲，但我们却先完成了，这实在是领先别人太多了，哈哈。</p>
<h1>3. 结尾想说的话</h1>
<p>结尾不想说话。但我想说。</p>
<p>我之前两天太累了，昨天也没休息好，今天又有点太努力了，所以我没有任何照片能放在这篇文章里。以后我找找有没有截图什么的吧。</p>
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="思录"/>
  </entry>
  <entry>
    <title>国际夏令营｜活着真好啊</title>
    <link href="https://luq-blog.xyz/posts/2026-07-11-sco-ysc-diary-07/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-07-11-sco-ysc-diary-07/</id>
    <published>2026-07-11T00:00:00.000Z</published>
    <updated>2026-07-11T00:00:00.000Z</updated>
    <summary>第一次爬这么陡的山，差点就回不来了。</summary>
    <content type="html"><![CDATA[<h2>1. 所以写一级标题有什么意义啊</h2>
<p><s>其实就是我不知道怎么写吧，哈哈</s>(￣﹃￣)</p>
<p>我想先感慨一下：:spoiler[真庆幸自己能活着回来！感觉差点昨天的文章就要变成绝笔了。]！</p>
<p>今天早上，我们去爬山。我还是不知道这里是哪里，但总之是一些山，一些很高、很陡的山。你可以看看我走到上面去拍的照片。不过，仅靠看照片是无法产生身临其境的体验的（除非你特别擅长这种想象）。个人认为，在当时，我的实际感受是仅看照片的感受的至少 5 倍。</p>
<p><img src="1.webp" alt="" /></p>
<p><img src="2.webp" alt="" /></p>
<p>你问我为什么要去爬这么陡的山！？因为别人都上去了，我要是不上去，岂不是显得很丢人吗？</p>
<p>所以，我只能硬着头皮上了。</p>
<p>其实上去的路不是很难。有时候比较滑，但是没有看上去那么滑。所以大多数时候都是直接走上去的，有时候可能需要趴着。不过下去的时候就非常困难了。我有点恐高，同时看到非常陡峭的路，我就感觉自己会打滑。所以有很多次我都害怕得不敢下去。如果不是志愿者的帮助，我甚至觉得自己都下不来了。</p>
<p>这真是一次难忘的经历，不过我再也不想经历这样的事了つ﹏⊂</p>
<p>感觉今天的肾上腺素已经在爬山这件事上用尽了，所以现在很疲惫。文章就写到这里吧，我实在不想多写了。</p>
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="思录"/>
  </entry>
  <entry>
    <title>国际夏令营｜外出旅行</title>
    <link href="https://luq-blog.xyz/posts/2026-07-10-sco-ysc-diary-06/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-07-10-sco-ysc-diary-06/</id>
    <published>2026-07-10T00:00:00.000Z</published>
    <updated>2026-07-10T00:00:00.000Z</updated>
    <summary>记录第一次参加国际夏令营的经历。</summary>
    <content type="html"><![CDATA[<h1>1. 我今天不想写标题</h1>
<p>所以我就混到一起写长（liu）文（shui）章（zhang）好了。</p>
<p>今天和明天，我们都不在学校，因为我们要外出旅行。看了一下计划，我们居然要在 2 天之内绕湖一周，这可是很远的路程啊。</p>
<p><img src="1.webp" alt="" /></p>
<p>事实证明，我们很长一段时间都在赶路，坐车的时间加起来可能有超过 4 小时了吧(￣y▽,￣)╭ 好累啊。</p>
<p>坐车的这段时间，除了睡觉之外，我还看了 4 集动漫。果然啊，压力大的时候看番速度就会很快。</p>
<p>而且今天我才发现，不仅使用B站有些番看不了，而且网易云音乐也有些音乐由于版权问题在这里播放不了。</p>
<p>啊......写到这里我才想起来，关于今天旅行的事情是不是还没有写(￣▽￣)”所以接下来我就要写今天的行程了。</p>
<p>首先，我们吃了早饭。今天早上我终于想起来带手机了，哈哈。</p>
<p><img src="2.webp" alt="" /></p>
<p>然后我们坐了将近 2 小时的车，到了一个吃饭的地方。吃午饭。（不要问我在哪里，因为这里的地名我不会读O_O）</p>
<p>在这里，我们吃到的东西和第一天路上吃的饭很相似。都是一道一道上菜，最初都是沙拉，接着还是放土豆的羊肉汤。等了很长时间后，终于上了主菜——土豆烧牛肉（当地可能不是这么叫的，毕竟是他们的国菜）</p>
<blockquote>
<p>照片就放在这里了。他们怎么这么喜欢用土豆做菜啊，哈哈。</p>
<p><img src="3.webp" alt="" /></p>
<p><img src="4.webp" alt="" /></p>
<p><img src="5.webp" alt="" /></p>
</blockquote>
<p>吃完午饭后，我们出发去...呃...下一个地方。</p>
<p>到达的地方应该是一个自然景区，风景还不错，而且没有旅游业发展的副作用。<s>也有可能是旅游业没怎么发展，哈哈</s>。我随手拍了一些照片。</p>
<blockquote>
<p>山。</p>
<p><img src="6.webp" alt="" />
<img src="7.webp" alt="" />
<img src="8.webp" alt="" />
<img src="9.webp" alt="" />
<img src="10.webp" alt="" />
<img src="11.webp" alt="" /></p>
<p>有水的山。
<img src="12.webp" alt="" />
<img src="13.webp" alt="" />
<img src="14.webp" alt="" /></p>
</blockquote>
<p>我们在车上坐了将近 3 个小时，到了我们今天晚上住的地方。我实在没想到，这个国家的人居然会在海边建立都是毡房的度假区。也没想到他们的毡房居然这么好，比那种最原始的好太多了！</p>
<p>我们这里有电，有独立卫浴，甚至还有空调。随便放一些照片。</p>
<blockquote>
<p><img src="15.webp" alt="" />
<img src="16.webp" alt="" />
<img src="17.webp" alt="" /></p>
</blockquote>
<p>然后这里的可乐居然 3 美元一瓶，价格太吓人了吧。</p>
<p>就到这里吧，我不想写了。我要去写代码了。按照工作流程，现在的任务在我手上，所以我今天还有事要做。</p>
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="思录"/>
  </entry>
  <entry>
    <title>天文数据科学｜机器学习甄别系外行星</title>
    <link href="https://luq-blog.xyz/posts/2026-07-09-astro-data-science-03-machine-learning-classifier/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-07-09-astro-data-science-03-machine-learning-classifier/</id>
    <published>2026-07-09T00:00:00.000Z</published>
    <updated>2026-07-09T00:00:00.000Z</updated>
    <summary>本文介绍如何用 Pandas DataFrame 组织系外行星候选体数据，并通过随机森林分类器自动甄别真实行星与假阳性信号，实现从手动识别到机器学习自动分类的跨越。</summary>
    <content type="html"><![CDATA[<p>在前两篇文章中，我们通过肉眼和 BLS 算法分析了单颗恒星的光变曲线。但在真实的 Kepler 任务中，望远镜发现了<strong>数千个候选信号</strong>——其中许多是假阳性（如食双星、仪器噪声等）。</p>
<p>本文的目标是：利用 <strong>Pandas DataFrame</strong> 组织大规模候选体数据，训练一个<strong>随机森林分类器</strong>，让机器学习模型自动甄别真实的系外行星。</p>
<hr />
<h1>1. DataFrame 热身练习</h1>
<h2>1.1 创建玩具数据集</h2>
<p>用三个模拟的系外行星候选体数据来熟悉 DataFrame 的基本操作。每条记录包含周期、凌星深度、持续时间、信噪比和文字描述：</p>
<pre><code>import pandas as pd

warmup = pd.DataFrame([
    {
        "case": "A",
        "period_days": 3.2,
        "depth_percent": 0.8,
        "duration_hours": 2.1,
        "snr": 14.0,
        "description": "regular repeated U-shaped dips"
    },
    {
        "case": "B",
        "period_days": 8.5,
        "depth_percent": 0.1,
        "duration_hours": 1.0,
        "snr": 2.2,
        "description": "weak messy signal, not clearly repeating"
    },
    {
        "case": "C",
        "period_days": 1.7,
        "depth_percent": 8.5,
        "duration_hours": 6.0,
        "snr": 20.0,
        "description": "very deep V-shaped repeated dips"
    },
])

warmup
</code></pre>
<p>输出：</p>
<pre><code>  case  period_days  depth_percent  duration_hours   snr  \
0    A          3.2            0.8             2.1  14.0
1    B          8.5            0.1             1.0   2.2
2    C          1.7            8.5             6.0  20.0

                                description
0            regular repeated U-shaped dips
1  weak messy signal, not clearly repeating
2          very deep V-shaped repeated dips
</code></pre>
<h2>1.2 添加新列</h2>
<p>为每个案例添加人工标注的分类标签。在天文学中，常见的分类包括：</p>
<ul>
<li><code>candidate_like</code>：类似真实行星候选体（U 形凹陷、高 SNR、周期性）</li>
<li><code>weak_or_noise</code>：信号太弱或噪声（低 SNR、无清晰重复）</li>
<li><code>possible_binary</code>：可能是食双星（很深的 V 形凹陷）</li>
</ul>
<pre><code>toy_df = warmup.copy()

toy_df["human_label"] = [
    "candidate_like",
    "weak_or_noise",
    "possible_binary"
]

toy_df
</code></pre>
<p>输出：</p>
<pre><code>  case  period_days  depth_percent  duration_hours   snr  \
0    A          3.2            0.8             2.1  14.0
1    B          8.5            0.1             1.0   2.2
2    C          1.7            8.5             6.0  20.0

                                description      human_label
0            regular repeated U-shaped dips   candidate_like
1  weak messy signal, not clearly repeating    weak_or_noise
2          very deep V-shaped repeated dips  possible_binary
</code></pre>
<h2>1.3 DataFrame 基础属性</h2>
<pre><code>print("Shape: ", toy_df.shape)
print('Rows:', toy_df.shape[0])
print('Columns:', toy_df.shape[1])
</code></pre>
<p>输出：</p>
<pre><code>Shape:  (3, 7)
Rows: 3
Columns: 7
</code></pre>
<p><code>shape</code> 返回 (行数, 列数) 元组。</p>
<h2>1.4 列筛选</h2>
<p>选取特定列的子集。双层方括号 <code>df[['col1', 'col2']]</code> 返回 DataFrame，单层方括号 <code>df['col']</code> 返回 Series：</p>
<pre><code>toy_df[['case', 'period_days', 'depth_percent', 'snr']]
</code></pre>
<p>输出：</p>
<pre><code>  case  period_days  depth_percent   snr
0    A          3.2            0.8  14.0
1    B          8.5            0.1   2.2
2    C          1.7            8.5  20.0
</code></pre>
<h2>1.5 特征矩阵与标签向量</h2>
<p>在机器学习中，特征（Features）是输入模型的数值列，标签（Labels）是要预测的目标：</p>
<pre><code>feature_columns = [
    'period_days',
    'depth_percent',
    'duration_hours',
    'snr'
]

toy_features = toy_df[feature_columns]
toy_features
</code></pre>
<p>输出：</p>
<pre><code>   period_days  depth_percent  duration_hours   snr
0          3.2            0.8             2.1  14.0
1          8.5            0.1             1.0   2.2
2          1.7            8.5             6.0  20.0
</code></pre>
<pre><code>toy_labels = toy_df['human_label']
toy_labels
</code></pre>
<p>输出：</p>
<pre><code>0     candidate_like
1      weak_or_noise
2    possible_binary
Name: human_label, dtype: object
</code></pre>
<p>这就是监督学习的基本数据结构：<strong>X（特征矩阵）</strong> 和 <strong>y（标签向量）</strong>。</p>
<h2>1.6 布尔条件筛选</h2>
<pre><code>high_snr = toy_df[toy_df['snr'] &gt; 5]
high_snr
</code></pre>
<p>输出：</p>
<pre><code>  case  period_days  depth_percent  duration_hours   snr  \
0    A          3.2            0.8             2.1  14.0
2    C          1.7            8.5             6.0  20.0

                        description      human_label
0    regular repeated U-shaped dips   candidate_like
2  very deep V-shaped repeated dips  possible_binary
</code></pre>
<pre><code>weak_signals = toy_df[toy_df['snr'] &lt;= 5]
weak_signals
</code></pre>
<p>输出：</p>
<pre><code>  case  period_days  depth_percent  duration_hours  snr  \
1    B          8.5            0.1             1.0  2.2

                                description    human_label
1  weak messy signal, not clearly repeating  weak_or_noise
</code></pre>
<h2>1.7 多条件组合筛选</h2>
<p>用 <code>&amp;</code>（与）和括号组合多个条件：</p>
<pre><code>deep_signals = toy_df[toy_df['depth_percent'] &gt; 2.0]
deep_signals
</code></pre>
<pre><code>interesting = toy_df[
    (toy_df["snr"] &gt; 5) &amp;
    (toy_df["depth_percent"] &lt; 5)
]

interesting
</code></pre>
<p>输出：</p>
<pre><code>  case  period_days  depth_percent  duration_hours   snr  \
0    A          3.2            0.8             2.1  14.0

                      description     human_label
0  regular repeated U-shaped dips  candidate_like
</code></pre>
<p>这个查询找出了"高 SNR 但深度适中"的信号——最有可能是真实行星的候选体。</p>
<h2>1.8 排序</h2>
<pre><code>toy_df.sort_values("snr", ascending=False)
</code></pre>
<p>输出：</p>
<pre><code>  case  period_days  depth_percent  duration_hours   snr  \
2    C          1.7            8.5             6.0  20.0
0    A          3.2            0.8             2.1  14.0
1    B          8.5            0.1             1.0   2.2

                                description      human_label
2          very deep V-shaped repeated dips  possible_binary
0            regular repeated U-shaped dips   candidate_like
1  weak messy signal, not clearly repeating    weak_or_noise
</code></pre>
<pre><code>toy_df.sort_values("depth_percent", ascending=False)
</code></pre>
<h2>1.9 值计数与布尔列</h2>
<pre><code>toy_df["human_label"].value_counts()
</code></pre>
<p>输出：</p>
<pre><code>human_label
candidate_like     1
weak_or_noise      1
possible_binary    1
Name: count, dtype: int64
</code></pre>
<p>创建布尔列作为派生特征：</p>
<pre><code>toy_df["strong_signal"] = toy_df["snr"] &gt;= 7

toy_df[["case", "snr", "strong_signal", "human_label"]]
</code></pre>
<p>输出：</p>
<pre><code>  case   snr  strong_signal      human_label
0    A  14.0           True   candidate_like
1    B   2.2          False    weak_or_noise
2    C  20.0           True  possible_binary
</code></pre>
<pre><code>toy_df["very_deep"] = toy_df["depth_percent"] &gt; 5

toy_df[["case", "depth_percent", "very_deep", "human_label"]]
</code></pre>
<h2>1.10 列运算与分组聚合</h2>
<p>创建派生数值列——简单评分 = SNR − 深度：</p>
<pre><code>toy_df["simple_score"] = toy_df["snr"] - toy_df["depth_percent"]

toy_df[["case", "snr", "depth_percent", "simple_score", "human_label"]]
</code></pre>
<p>输出：</p>
<pre><code>  case   snr  depth_percent  simple_score      human_label
0    A  14.0            0.8          13.2   candidate_like
1    B   2.2            0.1           2.1    weak_or_noise
2    C  20.0            8.5          11.5  possible_binary
</code></pre>
<p>按类别分组计算均值：</p>
<pre><code>toy_df.groupby("human_label")[["period_days", "depth_percent", "duration_hours", "snr"]].mean()
</code></pre>
<p>输出：</p>
<pre><code>                 period_days  depth_percent  duration_hours   snr
human_label
candidate_like           3.2            0.8             2.1  14.0
possible_binary          1.7            8.5             6.0  20.0
weak_or_noise            8.5            0.1             1.0   2.2
</code></pre>
<p><code>groupby("human_label").mean()</code> 按分类标签分组后计算每个特征的平均值。可以看到：<code>candidate_like</code> 类具有适中的深度和较高的 SNR，而 <code>possible_binary</code> 类深度很大。</p>
<h2>1.11 准备机器学习数据结构</h2>
<pre><code>X_toy = toy_df[feature_columns]
y_toy = toy_df["human_label"]

print("X shape:", X_toy.shape)
print("y shape:", y_toy.shape)

display(X_toy)
display(y_toy)
</code></pre>
<p>输出：</p>
<pre><code>X shape: (3, 4)
y shape: (3,)

   period_days  depth_percent  duration_hours   snr
0          3.2            0.8             2.1  14.0
1          8.5            0.1             1.0   2.2
2          1.7            8.5             6.0  20.0

0     candidate_like
1      weak_or_noise
2    possible_binary
Name: human_label, dtype: object
</code></pre>
<hr />
<h1>2. 大规模真实数据</h1>
<h2>2.1 环境配置</h2>
<p>安装 scikit-learn 并导入所需库：</p>
<pre><code>!pip -q install scikit-learn
</code></pre>
<pre><code>import warnings
warnings.filterwarnings("ignore")

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

import requests
from io import StringIO
from IPython.display import display

from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import (
    accuracy_score,
    precision_score,
    recall_score,
    classification_report,
    confusion_matrix,
    ConfusionMatrixDisplay,
)

RANDOM_STATE = 42

print("Libraries imported successfully.")
print("numpy:", np.__version__)
print("pandas:", pd.__version__)
</code></pre>
<p>输出：</p>
<pre><code>Libraries imported successfully.
numpy: 2.3.5
pandas: 2.3.3
</code></pre>
<h2>2.2 查询 NASA 系外行星数据库</h2>
<p>从 NASA Exoplanet Archive 下载 5000 条已知分类的记录——包括已确认行星（CONFIRMED）、候选体（CANDIDATE）和假阳性（FALSE POSITIVE）：</p>
<pre><code>TAP_URL = "https://exoplanetarchive.ipac.caltech.edu/TAP/sync"

query = '''
select top 5000
    kepid,
    kepoi_name,
    kepler_name,
    koi_disposition,
    koi_period,
    koi_duration,
    koi_depth,
    koi_prad,
    koi_model_snr,
    koi_kepmag,
    koi_score
from cumulative
where koi_disposition in ('CONFIRMED', 'CANDIDATE', 'FALSE POSITIVE')
  and koi_period is not null
  and koi_duration is not null
  and koi_depth is not null
  and koi_prad is not null
  and koi_model_snr is not null
  and koi_kepmag is not null
'''
</code></pre>
<p>如果 NASA 查询失败，使用合成回退数据（<code>make_fallback_archive</code> 函数生成具有合理统计分布的人造数据）：</p>
<pre><code>def make_fallback_archive(n_confirmed=120, n_fp=120, n_candidate=60, seed=42):
    # Create a small synthetic fallback table if the live NASA query fails.
    rng = np.random.default_rng(seed)

    rows = []

    def add_rows(n, disposition):
        for i in range(n):
            if disposition == "CONFIRMED":
                period = 10 ** rng.uniform(np.log10(0.8), np.log10(25))
                duration = rng.uniform(1.0, 8.0)
                depth = 10 ** rng.uniform(np.log10(200), np.log10(12000))
                prad = 10 ** rng.uniform(np.log10(0.8), np.log10(12))
                snr = 10 ** rng.uniform(np.log10(7), np.log10(80))
                kepmag = rng.uniform(10, 15)
            elif disposition == "FALSE POSITIVE":
                period = 10 ** rng.uniform(np.log10(0.3), np.log10(40))
                duration = rng.uniform(0.5, 16.0)
                depth = 10 ** rng.uniform(np.log10(1000), np.log10(100000))
                prad = 10 ** rng.uniform(np.log10(5), np.log10(80))
                snr = 10 ** rng.uniform(np.log10(3), np.log10(120))
                kepmag = rng.uniform(11, 16)
            else:
                period = 10 ** rng.uniform(np.log10(0.5), np.log10(35))
                duration = rng.uniform(0.7, 12.0)
                depth = 10 ** rng.uniform(np.log10(200), np.log10(50000))
                prad = 10 ** rng.uniform(np.log10(1), np.log10(35))
                snr = 10 ** rng.uniform(np.log10(4), np.log10(90))
                kepmag = rng.uniform(10, 16)

            rows.append({
                "kepid": int(10_000_000 + len(rows)),
                "kepoi_name": f"K{len(rows):05d}.01",
                "kepler_name": None if disposition != "CONFIRMED" else f"Kepler-{len(rows)} b",
                "koi_disposition": disposition,
                "koi_period": period,
                "koi_duration": duration,
                "koi_depth": depth,
                "koi_prad": prad,
                "koi_model_snr": snr,
                "koi_kepmag": kepmag,
                "koi_score": np.nan,
            })

    add_rows(n_confirmed, "CONFIRMED")
    add_rows(n_fp, "FALSE POSITIVE")
    add_rows(n_candidate, "CANDIDATE")

    return pd.DataFrame(rows)

archive_df = None

try:
    print("Querying NASA Exoplanet Archive...")
    response = requests.get(
        TAP_URL,
        params={"query": query, "format": "csv"},
        timeout=60,
    )
    response.raise_for_status()
    archive_df = pd.read_csv(StringIO(response.text))
    print("Live NASA rows downloaded:", len(archive_df))

except Exception as exc:
    print("NASA query failed. Using synthetic fallback data for the lesson.")
    print("Error:", exc)
    archive_df = make_fallback_archive()

print("Final table shape:", archive_df.shape)
archive_df.head()
</code></pre>
<p>输出：</p>
<pre><code>Querying NASA Exoplanet Archive...
Live NASA rows downloaded: 5000
Final table shape: (5000, 11)

      kepid kepoi_name   kepler_name koi_disposition  koi_period  \
0  10797460  K00752.01  Kepler-227 b       CONFIRMED    9.488036
1  10797460  K00752.02  Kepler-227 c       CONFIRMED   54.418383
2  10811496  K00753.01           NaN       CANDIDATE   19.899140
3  10848459  K00754.01           NaN  FALSE POSITIVE    1.736952
4  10854555  K00755.01  Kepler-664 b       CONFIRMED    2.525592

   koi_duration  koi_depth  koi_prad  koi_model_snr  koi_kepmag  koi_score
0       2.95750      615.8      2.26           35.8      15.347      1.000
1       4.50700      874.8      2.83           25.8      15.347      0.969
2       1.78220    10829.0     14.60           76.3      15.436      0.000
3       2.40641     8079.2     33.46          505.6      15.597      0.000
4       1.65450      603.3      2.75           40.9      15.509      1.000
</code></pre>
<pre><code>print('Columns:', list(archive_df.columns))
</code></pre>
<p>输出：</p>
<pre><code>Columns: ['kepid', 'kepoi_name', 'kepler_name', 'koi_disposition', 'koi_period', 'koi_duration', 'koi_depth', 'koi_prad', 'koi_model_snr', 'koi_kepmag', 'koi_score']
</code></pre>
<hr />
<h1>3. 特征工程</h1>
<h2>3.1 对数变换</h2>
<p>天文学数据通常是<strong>对数正态分布</strong>（数量级跨越很大）。取对数后数据更接近正态分布，模型训练效果更好：</p>
<pre><code>def safe_log10(x):
    # Log10 transform that avoids problems with zero or negative values.
    x = pd.to_numeric(x, errors="coerce")
    return np.log10(np.clip(x, 1e-12, None))

features_df = archive_df.copy()

features_df["log_period"] = safe_log10(features_df["koi_period"])
features_df["log_duration"] = safe_log10(features_df["koi_duration"])
features_df["log_depth"] = safe_log10(features_df["koi_depth"])
features_df["log_prad"] = safe_log10(features_df["koi_prad"])
features_df["log_snr"] = safe_log10(features_df["koi_model_snr"])
features_df["kepmag"] = pd.to_numeric(features_df["koi_kepmag"], errors="coerce")

FEATURES = [
    "log_period",
    "log_duration",
    "log_depth",
    "log_prad",
    "log_snr",
    "kepmag",
]

features_df = features_df.dropna(subset=FEATURES + ["koi_disposition"]).copy()

print("Feature columns:", FEATURES)
features_df[["kepoi_name", "koi_disposition"] + FEATURES].head()
</code></pre>
<p>输出：</p>
<pre><code>Feature columns: ['log_period', 'log_duration', 'log_depth', 'log_prad', 'log_snr', 'kepmag']

  kepoi_name koi_disposition  log_period  log_duration  log_depth  log_prad  \
0  K00752.01       CONFIRMED    0.977176      0.470925   2.789440  0.354108
1  K00752.02       CONFIRMED    1.735746      0.653888   2.941909  0.451786
2  K00753.01       CANDIDATE    1.298834      0.250956   4.034588  1.164353
3  K00754.01  FALSE POSITIVE    0.239788      0.381370   3.907368  1.524526
4  K00755.01       CONFIRMED    0.402363      0.218667   2.780533  0.439333

    log_snr  kepmag
0  1.553883  15.347
1  1.411620  15.347
2  1.882525  15.436
3  2.703807  15.597
4  1.611723  15.509
</code></pre>
<p><code>np.clip(x, 1e-12, None)</code> 将所有 ≤0 的值替换为一个极小的正数（10⁻¹²），避免 <code>log10(0)</code> 或 <code>log10(负数)</code> 的数学错误。<code>safe_log10</code> 封装了这一安全处理。</p>
<h2>3.2 划分训练集</h2>
<p>只使用已知标签的数据（CONFIRMED = 1，FALSE POSITIVE = 0）作为训练集，CANDIDATE 留作后续预测：</p>
<pre><code>known_df = features_df[
    features_df["koi_disposition"].isin(["CONFIRMED", "FALSE POSITIVE"])
].copy()

candidate_df = features_df[
    features_df["koi_disposition"] == "CANDIDATE"
].copy()

known_df["label"] = (known_df["koi_disposition"] == "CONFIRMED").astype(int)

print("Known examples:", known_df.shape)
print("Candidate rows for later ranking:", candidate_df.shape)
print("\nLabel counts:")
print(known_df["label"].value_counts().rename(index={0: "false positive", 1: "confirmed"}))
</code></pre>
<p>输出：</p>
<pre><code>Known examples: (4194, 18)
Candidate rows for later ranking: (806, 17)

Label counts:
label
confirmed         2526
false positive    1668
Name: count, dtype: int64
</code></pre>
<p><code>.astype(int)</code> 将布尔值 <code>True/False</code> 转换为 <code>1/0</code>，这是机器学习中二分类标签的标准格式。</p>
<h2>3.3 类别平衡</h2>
<p>如果确认行星（2526 个）远多于假阳性（1668 个），模型会偏向多数类。通过随机欠采样使两类数量相等：</p>
<pre><code>confirmed = known_df[known_df["label"] == 1]
false_positive = known_df[known_df["label"] == 0]

n = min(len(confirmed), len(false_positive))

if n &lt; 20:
    print("Warning: very few examples. The model will be only a toy demonstration.")

confirmed_bal = confirmed.sample(n=n, random_state=RANDOM_STATE)
false_positive_bal = false_positive.sample(n=n, random_state=RANDOM_STATE)

model_df = pd.concat([confirmed_bal, false_positive_bal], ignore_index=True)
model_df = model_df.sample(frac=1, random_state=RANDOM_STATE).reset_index(drop=True)

print("Balanced training table:", model_df.shape)
print(model_df["label"].value_counts().rename(index={0: "false positive", 1: "confirmed"}))

model_df[["kepoi_name", "koi_disposition"] + FEATURES + ["label"]].head()
</code></pre>
<p>输出：</p>
<pre><code>Balanced training table: (3336, 18)
label
confirmed         1668
false positive    1668
Name: count, dtype: int64

  kepoi_name koi_disposition  log_period  log_duration  log_depth  log_prad  \
0  K01441.01       CONFIRMED    0.929775      0.468052   2.470557  0.278754
1  K03084.01  FALSE POSITIVE    0.187637      0.231215   2.029789 -0.036212
2  K00082.04       CONFIRMED    0.849503      0.440437   1.817565 -0.200659
3  K03279.01       CONFIRMED    1.488943      0.624695   2.807061  0.383815
4  K00351.01       CONFIRMED    2.520611      1.158483   3.920233  1.037028

    log_snr  kepmag  label
0  1.315970  15.135      1
1  1.082785  14.991      0
2  1.350248  11.492      1
3  1.143015  15.765      1
4  2.550228  13.804      1
</code></pre>
<p><code>pd.concat</code> 拼接两个 DataFrame，<code>sample(frac=1)</code> 以 100% 比例打乱行顺序，<code>reset_index(drop=True)</code> 重置索引。</p>
<h2>3.4 特征空间可视化</h2>
<p>以 log_depth 和 log_snr 为坐标轴，观察两类样本的分布：</p>
<pre><code>plt.figure(figsize=(8, 5))

for label_value, label_name in [(1, "confirmed"), (0, "false positive")]:
    subset = model_df[model_df["label"] == label_value]
    plt.scatter(
        subset["log_depth"],
        subset["log_snr"],
        s=20,
        alpha=0.65,
        label=label_name,
    )

plt.xlabel("log10(transit depth [ppm])")
plt.ylabel("log10(model SNR)")
plt.title("Feature space: depth vs SNR")
plt.legend()
plt.grid(True)
plt.show()
</code></pre>
<p><img src="1.webp" alt="" /></p>
<p>已确认行星（蓝色）倾向于集中在特定的深度-SNR 区域，而假阳性（橙色）分布更分散——这为机器学习分类器提供了可学习的信息。</p>
<hr />
<h1>4. 训练随机森林分类器</h1>
<h2>4.1 训练/测试拆分</h2>
<p><code>train_test_split</code> 将数据随机分为训练集（75%）和测试集（25%），<code>stratify=y</code> 确保两个集合中类别比例一致：</p>
<pre><code>X = model_df[FEATURES]
y = model_df["label"]

X_train, X_test, y_train, y_test, train_idx, test_idx = train_test_split(
    X,
    y,
    model_df.index,
    test_size=0.25,
    random_state=RANDOM_STATE,
    stratify=y,
)

print("Training examples:", len(X_train))
print("Test examples:", len(X_test))
print("\nTrain label counts:")
print(y_train.value_counts().rename(index={0: "false positive", 1: "confirmed"}))
print("\nTest label counts:")
print(y_test.value_counts().rename(index={0: "false positive", 1: "confirmed"}))
</code></pre>
<p>输出：</p>
<pre><code>Training examples: 2502
Test examples: 834

Train label counts:
label
false positive    1251
confirmed         1251
Name: count, dtype: int64

Test label counts:
label
false positive    417
confirmed         417
Name: count, dtype: int64
</code></pre>
<h2>4.2 训练模型</h2>
<p>随机森林（Random Forest）是多个决策树的集成，每棵树在数据的随机子集上训练。参数说明：</p>
<ul>
<li><code>n_estimators=300</code>：300 棵决策树</li>
<li><code>max_depth=5</code>：每棵树最多 5 层（防止过拟合）</li>
<li><code>min_samples_leaf=3</code>：叶节点最少 3 个样本</li>
<li><code>class_weight="balanced"</code>：自动处理类别不平衡</li>
</ul>
<pre><code>model = RandomForestClassifier(
    n_estimators=300,
    max_depth=5,
    min_samples_leaf=3,
    random_state=RANDOM_STATE,
    class_weight="balanced",
)

model.fit(X_train, y_train)

print("Random Forest trained.")
</code></pre>
<p>输出：</p>
<pre><code>Random Forest trained.
</code></pre>
<h2>4.3 模型评估</h2>
<p>在测试集上评估模型性能：</p>
<ul>
<li><strong>准确率（Accuracy）</strong>：预测正确的比例</li>
<li><strong>精确率（Precision）</strong>：预测为"确认行星"中真正是行星的比例</li>
<li><strong>召回率（Recall）</strong>：真实行星中被正确找出的比例</li>
</ul>
<pre><code>y_pred = model.predict(X_test)
y_proba = model.predict_proba(X_test)[:, 1]

acc = accuracy_score(y_test, y_pred)
prec = precision_score(y_test, y_pred, zero_division=0)
rec = recall_score(y_test, y_pred, zero_division=0)

print("Test accuracy:", round(acc, 3))
print("Test precision:", round(prec, 3))
print("Test recall:", round(rec, 3))

print("\nClassification report:")
print(classification_report(
    y_test,
    y_pred,
    target_names=["false positive", "confirmed"],
    zero_division=0,
))
</code></pre>
<p>输出：</p>
<pre><code>Test accuracy: 0.841
Test precision: 0.817
Test recall: 0.878

Classification report:
                precision    recall  f1-score   support

false positive       0.87      0.80      0.83       417
     confirmed       0.82      0.88      0.85       417

      accuracy                           0.84       834
     macro avg       0.84      0.84      0.84       834
  weighted avg       0.84      0.84      0.84       834
</code></pre>
<p><code>predict_proba</code> 返回每个样本属于各类的概率，<code>[:, 1]</code> 取第二列（确认行星的概率）。模型整体准确率 84%，对确认行星的召回率 88%——模型能找出大多数真实行星。</p>
<h2>4.4 混淆矩阵</h2>
<p>混淆矩阵可视化预测结果 vs 真实标签：</p>
<pre><code>cm = confusion_matrix(y_test, y_pred, labels=[1, 0])

fig, ax = plt.subplots(figsize=(5, 5))

disp = ConfusionMatrixDisplay(
    confusion_matrix=cm,
    display_labels=["confirmed", "false positive"],
)

disp.plot(ax=ax, values_format="d", colorbar=False)
plt.title("Confusion matrix — test set")
plt.grid(False)
plt.show()
</code></pre>
<p><img src="2.webp" alt="" /></p>
<ul>
<li>左上角（True Positive，366）：正确识别为行星</li>
<li>右下角（True Negative，335）：正确识别为假阳性</li>
<li>右上角（False Negative，51）：行星被误判为假阳性（漏报）</li>
<li>左下角（False Positive，82）：假阳性被误判为行星（误报）</li>
</ul>
<h2>4.5 分析分类错误的案例</h2>
<pre><code>test_rows = model_df.loc[test_idx].copy()
test_rows["true_label"] = y_test.values
test_rows["pred_label"] = y_pred
test_rows["confirmed_probability"] = y_proba

test_rows["correct"] = test_rows["true_label"] == test_rows["pred_label"]

mistakes = test_rows[~test_rows["correct"]].copy()

print("Number of test mistakes:", len(mistakes))

mistakes[[
    "kepid",
    "kepoi_name",
    "kepler_name",
    "koi_disposition",
    "koi_period",
    "koi_depth",
    "koi_prad",
    "koi_model_snr",
    "confirmed_probability",
    "true_label",
    "pred_label",
]].sort_values("confirmed_probability", ascending=False).head(20)
</code></pre>
<p>输出（前20条假阳性被误判为行星）：</p>
<pre><code>Number of test mistakes: 133

         kepid kepoi_name kepler_name koi_disposition  koi_period  koi_depth  \
2496   5443604  K02713.01         NaN  FALSE POSITIVE   21.391012      645.8
2139   8345172  K02385.01         NaN  FALSE POSITIVE   50.571581     1282.6
1707   3120276  K01111.01         NaN  FALSE POSITIVE   10.265477      399.6
...

      koi_prad  koi_model_snr  confirmed_probability  true_label  pred_label
2496      1.72           25.1               0.834551           0           1
2139      2.74           19.9               0.834471           0           1
</code></pre>
<p>这些被模型高置信度误判的假阳性，通常具有"看起来像行星"的物理参数：适中的周期、合理的凌星深度和小行星半径。这正是机器学习面临的挑战——<strong>有些假阳性在特征空间中与真实行星高度重叠</strong>。</p>
<h2>4.6 特征重要性</h2>
<p>随机森林可以告诉我们哪些特征对分类贡献最大：</p>
<pre><code>importance = pd.DataFrame({
    "feature": FEATURES,
    "importance": model.feature_importances_,
}).sort_values("importance", ascending=True)

plt.figure(figsize=(8, 4))
plt.barh(importance["feature"], importance["importance"])
plt.xlabel("Feature importance")
plt.title("Which features did the forest use most?")
plt.grid(axis="x", alpha=0.3)
plt.show()

importance.sort_values("importance", ascending=False)
</code></pre>
<p><img src="3.webp" alt="" /></p>
<p>通常 <code>log_depth</code>（凌星深度）和 <code>log_prad</code>（行星半径）是最重要的特征——<strong>深度越大、半径越大的信号越可能是假阳性（如食双星）</strong>。</p>
<h2>4.7 过拟合检查</h2>
<pre><code>train_pred = model.predict(X_train)
test_pred = model.predict(X_test)

train_acc = accuracy_score(y_train, train_pred)
test_acc = accuracy_score(y_test, test_pred)

print("Train accuracy:", round(train_acc, 3))
print("Test accuracy:", round(test_acc, 3))

if train_acc &gt; test_acc + 0.15:
    print("Possible overfitting: train score is much higher than test score.")
else:
    print("Train/test gap is not extremely large for this demo.")
</code></pre>
<p>输出：</p>
<pre><code>Train accuracy: 0.867
Test accuracy: 0.841
Train/test gap is not extremely large for this demo.
</code></pre>
<p>训练集（86.7%）与测试集（84.1%）差距仅 2.6 个百分点，模型未严重过拟合。</p>
<hr />
<h1>5. 候选体排序（Candidate Triage）</h1>
<h2>5.1 对未分类候选体进行预测</h2>
<p>Kepler 数据中有 806 条 CANDIDATE 状态的记录——它们的真实身份尚未被 NASA 确认。用训练好的模型对它们打分：</p>
<pre><code>if len(candidate_df) == 0:
    print("No candidate rows available. Skipping candidate triage.")
else:
    candidate_X = candidate_df[FEATURES]
    candidate_df = candidate_df.copy()
    candidate_df["confirmed_like_probability"] = model.predict_proba(candidate_X)[:, 1]

    ranked_candidates = candidate_df.sort_values("confirmed_like_probability", ascending=False)

    print("Ranked candidate rows:", len(ranked_candidates))

    display_cols = [
        "kepid",
        "kepoi_name",
        "kepler_name",
        "koi_period",
        "koi_depth",
        "koi_prad",
        "koi_model_snr",
        "koi_kepmag",
        "confirmed_like_probability",
    ]

    display(ranked_candidates[display_cols].head(15))
</code></pre>
<p>输出（前 15 个高概率候选体）：</p>
<pre><code>Ranked candidate rows: 806

         kepid kepoi_name kepler_name  koi_period  koi_depth  koi_prad  \
3144   4951877  K00501.01         NaN   24.796280      574.4      2.70
1561   4242147  K01934.01         NaN   28.782710      864.5      2.11
3400   5131180  K00641.01         NaN   14.851855     1341.0      2.73
...

      koi_model_snr  koi_kepmag  confirmed_like_probability
3144           43.6      14.612                    0.836956
1561           40.1      14.649                    0.836525
</code></pre>
<p>模型按概率从高到低排列了所有候选体。这些最有可能的候选体具有：适中的对数深度、小行星半径（1.5−3 地球半径）、高 SNR——与已确认行星的特征高度吻合，值得天文学家优先进行后续观测验证。</p>
<hr />
<h1>6. 本节小结</h1>
<p>本节完成了以下内容：</p>
<ul>
<li>掌握了 Pandas DataFrame 的核心操作：创建、列筛选、布尔过滤、排序、<code>groupby</code> 分组聚合、新列派生。</li>
<li>构建了特征矩阵（X）和标签向量（y），为监督学习做好了数据准备。</li>
<li>从 NASA Exoplanet Archive 获取了 5000 条真实系外行星候选体数据。</li>
<li>实施了特征工程：对数变换（<code>safe_log10</code>）使天文学数量级数据更适合机器学习模型。</li>
<li>训练了随机森林分类器（<code>RandomForestClassifier</code>），在测试集上达到 84% 准确率和 88% 召回率。</li>
<li>分析了混淆矩阵和特征重要性，理解了模型的优势和局限性。</li>
<li>对 806 个未确认候选体进行了概率排序，实现了自动化候选体筛选。</li>
</ul>
<hr />
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="信息学"/>
  </entry>
  <entry>
    <title>国际夏令营｜逐渐适应中。。。</title>
    <link href="https://luq-blog.xyz/posts/2026-07-09-sco-ysc-diary-05/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-07-09-sco-ysc-diary-05/</id>
    <published>2026-07-09T00:00:00.000Z</published>
    <updated>2026-07-09T00:00:00.000Z</updated>
    <summary>记录第一次参加国际夏令营的经历。</summary>
    <content type="html"><![CDATA[<h1>1. 必做任务－_－b</h1>
<p>明天我要向这里的外国同学多学习一点俄语，哈哈。</p>
<p>相关内容在正文部分讲述。</p>
<p><s>不过这里好像已经是正文了吧👈(⌒▽⌒)👉</s></p>
<p>另外老师让我把食物的照片放在文章里。</p>
<blockquote>
<p>早上又忘带手机了，哈哈(‧‧)nnn</p>
<p><img src="1.webp" alt="" /></p>
<p><img src="2.webp" alt="" /></p>
</blockquote>
<hr />
<h1>2. 真正的正文</h1>
<h2>2.1 课程进展！</h2>
<p>今天我还挺高兴的，因为今天听课的时候，突然发现我居然可以听懂大部分内容了。今天我挑战了一下不使用任何同声传译软件，虽然有些单词不认识，但是通过搜索个别单词，我就能领会老师的意思。</p>
<p>今天我们正式进入机器学习（ML）的学习（哈哈，这个表达怎么这么奇怪啊o_o ....）。我们了解了随机森林（Random Forest）算法的原理和实现方法，使用 NASA 的数据，训练并测试了模型。</p>
<p>感觉今天收获很大，因为这都是我之前没有了解过的东西。不得不说，Python 真是太厉害啦！各种功能，如果使用合适的库，只需要一行代码就可以搞定。</p>
<p>这里是代码：&lt;a href="/posts/2026-07-09-astro-data-science-03-machine-learning-classifier/"&gt;笔记｜天文数据科学03｜机器学习甄别系外行星&lt;/a&gt;</p>
<h2>2.2 团队协作</h2>
<p>我们的队长真是太厉害了。昨日我们只是大致确认了一下每个人的分工，今天我就收到了这样的文件：</p>
<blockquote>
<p><img src="3.webp" alt="" /></p>
<p><img src="4.webp" alt="" /></p>
</blockquote>
<p>显然，我是负责数据清洗和分析的。不过我觉得这样挺好，既不涉及到英文的数据库中寻找数据，也不需要未曾了解过的机器学习的知识。</p>
<p>队长是第一个环节的负责人，她今天已经完成了他负责的内容，然后把代码、数据和图片用电子邮箱发给我，剩下的就靠我了！</p>
<p>接下来几天一定不太好过吧，哈哈ˋ( ° ▽、° )</p>
<h2>2.3 计科大佬的演讲orz</h2>
<p>先来看看他的简历图片：</p>
<p><img src="5.webp" alt="" /></p>
<p>没错！就是这么厉害的人，我居然可以在现场听到他的演讲！！！</p>
<p><s>不过虽然不太能听懂他说的是什么</s></p>
<p>你问我他到底讲了什么？我不知道啊哈哈ψ(._. )&gt;</p>
<h2>2.4 社团活动</h2>
<p>今天写文章才想起来，我这些天都没把社团活动写进文章里。</p>
<p>今天我做了这个......emm...clay art</p>
<p><s>中文怎么退化了，我实在想不出来怎么说这个东西 in Chinese</s></p>
<p><img src="6.webp" alt="" /></p>
<h2>2.5 学习新语言</h2>
<p>这件事的起因是，一位吉尔吉斯的同学正在学习中文，正好我的英文也不太好。所以她用中文和我说话，我用英文和她说话。然后，我想，既然她都向我请教中文了，那我也向她学习一些俄语吧！</p>
<p>所以，我们就开始了互相教学......</p>
<p>我这次了解了字母表中前几个字母的读音，还知道了我的名字的俄语写法！虽然某些发音我很难读出来，但是总之就是很新奇(‾◡◝)</p>
<blockquote>
<p>一些个人信息隐藏了。包括姓名、肖像等。</p>
<p><img src="7.webp" alt="" /></p>
</blockquote>
<h2>2.6 我们学计算机的终于有课余时间啦！！！</h2>
<p>由于昨天下午拍照去了，其他同学的作业都没有按时完成，所以截止日期推迟到今天了。不过我昨天写完了，所以就相当于今天没有作业！（￣︶￣）↗　</p>
<p>在教室里，我的好朋友......正在给我演示如何玩东方的游戏。</p>
<p>我是不是也要变成车万人了！？</p>
<p><img src="8.webp" alt="" /></p>
<hr />
<h1>3. 总结</h1>
<p>（总结说，他今天不想工作，所以他休息了。再见。）</p>
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="思录"/>
  </entry>
  <entry>
    <title>天文数据科学｜周期侦探</title>
    <link href="https://luq-blog.xyz/posts/2026-07-08-astro-data-science-02-period-detective/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-07-08-astro-data-science-02-period-detective/</id>
    <published>2026-07-08T00:00:00.000Z</published>
    <updated>2026-07-08T00:00:00.000Z</updated>
    <summary>本文介绍系外行星探测的量化方法——通过残差平方和比较模型、利用BLS算法自动搜索周期性凌星信号，并使用真实的Kepler太空望远镜数据进行实战分析。</summary>
    <content type="html"><![CDATA[<p>在上一篇文章中，我们检查了光变曲线中的凌星信号。但真正的数据分析需要用数学来比较不同模型的优劣，并用计算机算法自动搜索周期性信号。</p>
<p>本文将逐步实现这些方法，最终用真实的 NASA Kepler 太空望远镜数据验证我们的技术。</p>
<hr />
<h1>1. 模型比较：哪个模型更好？</h1>
<h2>1.1 绘制观测数据</h2>
<p>首先创建一条简单的光变曲线——5个亮度的观测值随时间变化：</p>
<pre><code>import numpy as np
import matplotlib.pyplot as plt

time = np.array([1, 2, 3, 4, 5])
brightness = np.array([1.02, 0.98, 1.01, 0.99, 1.00])

plt.scatter(time, brightness)
plt.plot(time, brightness)

plt.xlabel('Time measurements')
plt.ylabel('Brightness')
</code></pre>
<p><img src="1.webp" alt="" /></p>
<p><code>plt.scatter()</code> 绘制散点（实际观测值），<code>plt.plot()</code> 用折线连接它们以展示变化趋势。亮度在 1.0 附近上下波动。</p>
<h2>1.2 最简单的模型：恒定亮度</h2>
<p>如果我们假设恒星亮度始终不变，那么可以构建一个"恒定模型"——将所有时间的预测值设为 1.00：</p>
<pre><code>model_brightness = 1.00
predicted = np.array([model_brightness] * len(brightness))

print(predicted)
plt.scatter(time, brightness)
plt.plot(time, predicted)
</code></pre>
<p><img src="2.webp" alt="" /></p>
<p>输出：</p>
<pre><code>[1. 1. 1. 1. 1.]
</code></pre>
<p><code>[model_brightness] * len(brightness)</code> 将标量值重复 5 次，创建一个长度匹配的数组。</p>
<h2>1.3 计算残差（误差）</h2>
<p>残差 = 预测值 − 观测值。正残差表示模型高估了亮度，负残差表示模型低估：</p>
<pre><code>error = predicted - brightness

print('Observed brightness', brightness)
print('Predicted brightness', predicted)
print('Errors', error)
</code></pre>
<p>输出：</p>
<pre><code>Observed brightness [1.02 0.98 1.01 0.99 1.  ]
Predicted brightness [1. 1. 1. 1. 1.]
Errors [-0.02  0.02 -0.01  0.01  0.  ]
</code></pre>
<h2>1.4 残差总和的问题</h2>
<p>将残差直接相加看似合理，但正负误差会相互抵消：</p>
<pre><code>total_error = np.sum(error)

print('Errors', error)
print('Sum of errors', total_error)
</code></pre>
<p>输出：</p>
<pre><code>Errors [-0.02  0.02 -0.01  0.01  0.  ]
Sum of errors 0.0
</code></pre>
<p>残差总和为 0，但模型显然不完美！因为正的 +0.02 和负的 −0.02 相互抵消了。因此不能直接使用残差总和来衡量模型好坏。</p>
<h2>1.5 残差平方和（SSE）</h2>
<p>解决方案：将误差<strong>平方</strong>后再求和。平方后所有值都为正，不会相互抵消：</p>
<p>$$\text{SSE} = \sum (\text{predicted} - \text{observed})^2$$</p>
<pre><code>squared_error = (predicted - brightness) ** 2
print('Errors:', error)
print('Squared errors:', squared_error)

sum_squared_error = np.sum(squared_error)
print('Sum of squared error:', sum_squared_error)
</code></pre>
<p>输出：</p>
<pre><code>Errors: [-0.02  0.02 -0.01  0.01  0.  ]
Squared errors: [0.0004 0.0004 0.0001 0.0001 0.    ]
Sum of squared error: 0.0010000000000000018
</code></pre>
<p>SSE 越小，模型越贴合数据。平方操作放大了大误差的影响，惩罚了偏离程度较大的点。</p>
<h2>1.6 比较两个模型</h2>
<p>现在比较两个恒定亮度模型：model_1 = 1.00、model_2 = 0.97：</p>
<pre><code>model_1 = 1.00
model_2 = 0.97

predicted_1 = np.array([model_1] * len(brightness))
predicted_2 = np.array([model_2] * len(brightness))
errors_1 = predicted_1 - brightness
errors_2 = predicted_2 - brightness

squared_error_1 = errors_1 ** 2
squared_error_2 = errors_2 ** 2

total_error_1 = np.sum(squared_error_1)
total_error_2 = np.sum(squared_error_2)

print('Errors_1:', total_error_1)
print('Errors_2:', total_error_2)

# model 1 is better
</code></pre>
<p>输出：</p>
<pre><code>Errors_1: 0.0010000000000000018
Errors_2: 0.00550000000000001
</code></pre>
<p>SSE₁ (0.001) &lt; SSE₂ (0.0055)，模型 1 更好。将两个模型可视化：</p>
<pre><code>plt.scatter(time, brightness)
plt.plot(time, predicted_1)
plt.plot(time, predicted_2)

plt.xlabel("Time")
plt.ylabel("Brightness")
plt.title("Which model is better?")
</code></pre>
<p><img src="3.webp" alt="" /></p>
<p>橙色线（model_2 = 0.97）整体低于数据点，SSE 更大。蓝色线（model_1 = 1.00）更接近数据中心。</p>
<h2>1.7 自动搜索最佳模型</h2>
<p>手动测试两个模型效率太低。可以用 <code>np.linspace</code> 在可能范围内生成 101 个候选值，遍历计算每个的 SSE，最后用 <code>np.argmin</code> 找到 SSE 最小的模型：</p>
<pre><code>possible_models = np.linspace(0.95, 1.05, 101)

sse_values = []

for model in possible_models:
    predicted = np.array([model] * len(brightness))
    errors = brightness - predicted
    sse = np.sum(errors ** 2)
    sse_values.append(sse)

sse_values = np.array(sse_values)

best_index = np.argmin(sse_values)
best_model = possible_models[best_index]
best_sse = sse_values[best_index]

print("Best model brightness:", best_model)
print("Smallest squared error:", best_sse)
</code></pre>
<p>输出：</p>
<pre><code>Best model brightness: 1.0
Smallest squared error: 0.0010000000000000018
</code></pre>
<p>最佳模型亮度 = 1.00，与我们之前的直观判断完全一致。这种"暴力搜索"方法虽然简单，但非常有效——它的核心思想与后续 BLS 算法搜索最佳周期一脉相承。</p>
<hr />
<h1>2. 比较含凌星与不含凌星的模型</h1>
<h2>2.1 生成含凌星的合成数据</h2>
<p>创建一个 100 个点的光变曲线，第 4.2 至 5.0 天存在凌星（亮度降为 0.97），并叠加测量噪声：</p>
<pre><code>time = np.linspace(0, 10, 100)
true_brightness = np.ones_like(time)

in_transit_true = (time &gt; 4.2) &amp; (time &lt; 5.0)
true_brightness[in_transit_true] = 0.97

rng = np.random.default_rng(42)
noise = rng.normal(0, 0.005, size=len(time))
brightness = true_brightness + noise

plt.scatter(time, brightness)
plt.show()
</code></pre>
<p><img src="4.webp" alt="" /></p>
<p><code>np.ones_like(time)</code> 创建与 time 形状相同的全 1 数组。<code>(time &gt; 4.2) &amp; (time &lt; 5.0)</code> 用布尔掩码标记凌星区间。</p>
<h2>2.2 比较两个模型</h2>
<p>"无凌星模型"假设亮度始终为 1.0；"有凌星模型"在第 4.2−5.0 天将亮度设为 0.97：</p>
<pre><code>model_no_transit = np.ones_like(time)
model_transit = np.ones_like(time)

in_transit_true = (time &gt; 4.2) &amp; (time &lt; 5.0)
model_transit[in_transit_true] = 0.97

sse_no_transit = np.sum((brightness - model_no_transit) ** 2)
sse_transit = np.sum((brightness - model_transit) ** 2)
print("SSE no transit model: ", sse_no_transit)
print("SSE transit model: ", sse_transit)

plt.scatter(time, brightness)
plt.plot(time, model_no_transit)
plt.plot(time, model_transit)
</code></pre>
<p><img src="5.webp" alt="" /></p>
<p>含凌星模型的 SSE 更小——它更好地解释了数据。这说明<strong>用模型的优劣可以量化判断是否存在凌星信号</strong>。</p>
<hr />
<h1>3. 真实 Kepler 数据分析</h1>
<h2>3.1 安装工具包</h2>
<p>从这一天开始，我们将使用真实的 NASA Kepler 太空望远镜数据。安装必要的 Python 包：</p>
<pre><code>!pip -q install lightkurve astroquery astropy
</code></pre>
<ul>
<li><code>lightkurve</code>：专门用于分析 Kepler/K2/TESS 光变曲线的 Python 库</li>
<li><code>astroquery</code>：查询 NASA 天体物理数据库</li>
<li><code>astropy</code>：天文学核心工具库</li>
</ul>
<pre><code>import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

import requests
from io import StringIO

import lightkurve as lk
from astropy.timeseries import BoxLeastSquares
from scipy.ndimage import median_filter

print("Tools are ready.")
</code></pre>
<p>输出：</p>
<pre><code>Tools are ready.
</code></pre>
<h2>3.2 选择团队与搜索范围</h2>
<p>每个团队被分配到不同的周期搜索范围，模拟真实科研中的分工协作：</p>
<pre><code>TEAM = 1  # change to 1, 2, or 3

TEAM_INFO = {
    1: {"emoji": "🔥", "name": "Brown dwarfs", "period_range": (0.5, 3.0)},
    2: {"emoji": "🌡", "name": "We came here for free wi-fi ", "period_range": (3.0, 10.0)},
    3: {"emoji": "🪐", "name": " Trinity ", "period_range": (10.0, 30.0)},
}

info = TEAM_INFO[TEAM]
p_lo, p_hi = info["period_range"]

print(f"Team {TEAM} {info['emoji']} — {info['name']}")
print(f"Period range: {p_lo} to {p_hi} days")
</code></pre>
<p>输出：</p>
<pre><code>Team 1 🔥 — Brown dwarfs
Period range: 0.5 to 3.0 days
</code></pre>
<h2>3.3 查询 NASA 系外行星数据库</h2>
<p>通过 NASA Exoplanet Archive 的 TAP（Table Access Protocol）接口，使用 SQL 查询已确认的系外行星：</p>
<pre><code>TAP_URL = "https://exoplanetarchive.ipac.caltech.edu/TAP/sync"

query = f"""
select top 20
    kepid,
    kepoi_name,
    kepler_name,
    koi_disposition,
    koi_period,
    koi_duration,
    koi_depth,
    koi_prad,
    koi_score,
    koi_kepmag
from cumulative
where koi_disposition = 'CONFIRMED'
  and koi_period is not null
  and koi_depth is not null
  and koi_period &gt;= {p_lo}
  and koi_period &lt; {p_hi}
  and koi_depth &gt; 1000
order by koi_depth desc
"""

response = requests.get(
    TAP_URL,
    params={"query": query, "format": "csv"},
    timeout=60
)

response.raise_for_status()

archive_df = pd.read_csv(StringIO(response.text))

print("Downloaded targets:", len(archive_df))

archive_df[[
    "kepid",
    "kepoi_name",
    "kepler_name",
    "koi_disposition",
    "koi_period",
    "koi_duration",
    "koi_depth",
    "koi_prad",
    "koi_score"
]].head(10)
</code></pre>
<p>输出：</p>
<pre><code>Downloaded targets: 20

      kepid kepoi_name   kepler_name koi_disposition  koi_period  \
0   5794240  K00254.01   Kepler-45 b       CONFIRMED    2.455241
1   3749365  K01176.01  Kepler-785 b       CONFIRMED    1.973761
2  11517719  K01416.01  Kepler-840 b       CONFIRMED    2.495780
3  10619192  K00203.01   Kepler-17 b       CONFIRMED    1.485711
4   7532973  K01450.01  Kepler-854 b       CONFIRMED    2.144632
5   9651668  K00183.01  Kepler-423 b       CONFIRMED    2.684329
6  11414511  K00767.01  Kepler-670 b       CONFIRMED    2.816505
7   3935914  K00809.01  Kepler-686 b       CONFIRMED    1.594745
8   7849854  K00897.01  Kepler-718 b       CONFIRMED    2.052350
9  11446443  K00001.01    Kepler-1 b       CONFIRMED    2.470613

   koi_duration  koi_depth  koi_prad  koi_score
0       1.72722    36911.9     10.60      0.999
1       1.82542    30222.4      8.78      0.998
2       4.27210    25070.0     15.75      0.920
3       2.28740    20823.9     14.48      1.000
4       6.27010    19836.9     59.19      0.041
5       2.69382    18136.5     12.73      0.995
6       2.50587    16813.7     12.21      1.000
7       1.96040    15044.5     11.30      1.000
8       2.07780    14280.6     13.12      1.000
9       1.74319    14230.9     13.04      0.811
</code></pre>
<p><code>requests.get</code> 发送 HTTP GET 请求，<code>pd.read_csv(StringIO(response.text))</code> 将返回的 CSV 文本解析为 DataFrame。<code>koi_depth</code> 单位为 ppm（百万分之一），如 36911.9 ppm = 3.69%。</p>
<h2>3.4 选择目标</h2>
<p>选取第 8 颗星（Kepler-686 b）作为分析目标：</p>
<pre><code>TARGET_INDEX = 7

target = archive_df.iloc[TARGET_INDEX]

TARGET_KEPID = int(target["kepid"])
TARGET_KOI = target["kepoi_name"]
TARGET_NAME = target["kepler_name"]
TARGET_PERIOD = float(target["koi_period"])
TARGET_DURATION_HOURS = float(target["koi_duration"])
TARGET_DEPTH_PPM = float(target["koi_depth"])

print("Selected target")
print("----------------")
print("KIC ID:", TARGET_KEPID)
print("KOI name:", TARGET_KOI)
print("Kepler name:", TARGET_NAME)
print("NASA period:", TARGET_PERIOD, "days")
print("NASA duration:", TARGET_DURATION_HOURS, "hours")
print("NASA depth:", TARGET_DEPTH_PPM, "ppm")
print("NASA depth:", TARGET_DEPTH_PPM / 10000, "%")
</code></pre>
<p>输出：</p>
<pre><code>Selected target
----------------
KIC ID: 3935914
KOI name: K00809.01
Kepler name: Kepler-686 b
NASA period: 1.594745374 days
NASA duration: 1.9604 hours
NASA depth: 15044.5 ppm
NASA depth: 1.50445 %
</code></pre>
<p>Kepler-686 b 的公转周期约 1.59 天（超短周期行星），凌星持续约 1.96 小时，深度约 1.5%。</p>
<h2>3.5 下载 Kepler 光变曲线数据</h2>
<p><code>lightkurve</code> 库可以直接从 MAST（Mikulski Archive for Space Telescopes）下载 Kepler 数据：</p>
<pre><code>search_result = lk.search_lightcurve(
    f"KIC {TARGET_KEPID}",
    mission="Kepler",
    cadence="long"
)

print(search_result)

lc_raw = search_result[0].download(
    quality_bitmask="default",
    flux_column="pdcsap_flux"
)

print(lc_raw)
</code></pre>
<p>输出（部分）：</p>
<pre><code>SearchResult containing 15 data products.

 #       mission      year author exptime  target_name  distance
                                     s                   arcsec
--- ----------------- ---- ------ ------- ------------- --------
  0 Kepler Quarter 02 2009 Kepler    1800 kplr003935914      0.0
  1 Kepler Quarter 03 2009 Kepler    1800 kplr003935914      0.0
  ...
 14 Kepler Quarter 17 2013 Kepler    1800 kplr003935914      0.0
       time             flux      ...   pos_corr1      pos_corr2
                    electron / s  ...      pix            pix
------------------ -------------- ... -------------- --------------
169.76597543497337  7.6352046e+03 ... -5.9283964e-02  2.2301620e-01
169.78640959970653  7.6372656e+03 ... -5.8712441e-02  2.2273061e-01
...
Length = 4075 rows
</code></pre>
<p>Kepler 在 15 个季度（Quarter）中观测了这颗星，每季度约 90 天。<code>cadence="long"</code> 表示 30 分钟长曝光模式。<code>pdcsap_flux</code> 是经过系统误差校正的流量数据。</p>
<h2>3.6 数据清洗</h2>
<p>去除 NaN，并用中位数归一化：</p>
<pre><code>time = lc_raw.time.value
flux = lc_raw.flux.value

ok = np.isfinite(time) &amp; np.isfinite(flux)

time = time[ok]
flux = flux[ok]

flux = flux / np.nanmedian(flux)

print("Number of points:", len(time))
print("Time range:", time.min(), "to", time.max())
print("Median normalized flux:", np.nanmedian(flux))

plt.scatter(time, flux, s=1)
plt.xlabel('Time')
plt.ylabel('Normalized brightness')

plt.title('Raw Kepler Data')
</code></pre>
<p><img src="6.webp" alt="" /></p>
<hr />
<h1>4. 相位折叠</h1>
<h2>4.1 计算相位的函数</h2>
<p>如果知道行星的精确周期，可以将所有数据折叠到一个周期内——这就是<strong>相位折叠</strong>。<code>%</code> 取模运算符将时间"折叠"到一个周期内：</p>
<pre><code>def compute_phase(time, period, t0=0):
    """
    Convert time into phase between 0 and 1.
    Phase tells us where each point is inside one repeated cycle.
    """
    return ((time - t0) % period) / period
</code></pre>
<p>相位 0 到 1 表示一个完整周期的位置。<code>% period</code> 将任意时间映射回 [0, period) 区间，<code>/ period</code> 归一化到 [0, 1)。</p>
<h2>4.2 试错法：用不同周期折叠</h2>
<p>比较三个周期：偏短、偏长、以及 NASA 目录中的正确周期：</p>
<pre><code>trial_periods = [
    TARGET_PERIOD * 0.83,
    TARGET_PERIOD * 1.31,
    TARGET_PERIOD
]

labels = [
    "wrong period: too short",
    "wrong period: too long",
    "NASA catalog period"
]

fig, axes = plt.subplots(1, 3, figsize=(15, 4), sharey=True)

for ax, period_try, label in zip(axes, trial_periods, labels):
    phase = compute_phase(time, period_try, time[0])
    ax.scatter(phase, flux, s=2, alpha=0.25)
    ax.set_title(f"{label}\nP = {period_try:.3f} days")
    ax.set_xlabel("Phase")
    ax.grid(True)

axes[0].set_ylabel("Normalized brightness")

plt.suptitle(f"{TARGET_NAME} — manual folding at three trial periods")
plt.tight_layout()
plt.show()
</code></pre>
<p><img src="7.webp" alt="" /></p>
<p><strong>只有使用正确周期时，凌星信号才会在相位图中清晰聚集在某一位置。</strong> 错误的周期会导致凌星数据点散布在整个相位空间中，无法形成明显的凹陷。</p>
<hr />
<h1>5. BLS 自动周期搜索</h1>
<h2>5.1 深度清洗</h2>
<p>在进行自动搜索前，需要进一步清洗数据——去除极端异常值并使用中值滤波消除缓慢趋势：</p>
<pre><code># Step 1: remove extreme outliers
median_flux = np.nanmedian(flux)
std_flux = np.nanstd(flux)

mask = np.abs(flux - median_flux) &lt; 5 * std_flux

time_clean = time[mask]
flux_clean = flux[mask]

# Step 2: flatten slow trends with median filter
window = 301

trend = median_filter(flux_clean, size=window)
trend[trend == 0] = 1.0

flux_clean = flux_clean / trend
flux_clean = flux_clean / np.nanmedian(flux_clean)

print("Before cleaning:", len(time), "points")
print("After cleaning:", len(time_clean), "points")
print("Raw flux std:", np.nanstd(flux))
print("Clean flux std:", np.nanstd(flux_clean))
</code></pre>
<p>输出：</p>
<pre><code>Before cleaning: 4070 points
After cleaning: 4046 points
Raw flux std: 0.003044938
Clean flux std: 0.0025537307
</code></pre>
<p><code>median_filter</code> 是 <code>scipy.ndimage</code> 提供的中值滤波器，<code>size=301</code> 表示用 301 个相邻点的中位数替代每个点，从而平滑掉缓慢的亮度趋势。清洗后标准差从 0.0030 降至 0.0026：</p>
<pre><code>fig, axes = plt.subplots(2, 1, figsize=(12, 7), sharex=True)

axes[0].scatter(time, flux, s=3, alpha=0.4)
axes[0].set_ylabel("Raw flux")
axes[0].set_title("Before cleaning")
axes[0].grid(True)

axes[1].scatter(time_clean, flux_clean, s=3, alpha=0.4)
axes[1].set_xlabel("Time [days]")
axes[1].set_ylabel("Cleaned flux")
axes[1].set_title("After simple cleaning / flattening")
axes[1].grid(True)

plt.tight_layout()
plt.show()
</code></pre>
<p><img src="8.webp" alt="" /></p>
<h2>5.2 BLS 搜索</h2>
<p>**BLS（Box Least Squares，盒子最小二乘）**是系外行星探测中最经典的自动搜索算法。它的核心思想：用一个"盒子形"模型（亮度在凌星期间下降，其它时间正常），在不同周期和持续时间下进行拟合，找到 SSE 最小的参数组合：</p>
<pre><code>bls_lo = max(0.2, TARGET_PERIOD * 0.7)
bls_hi = TARGET_PERIOD * 1.3

periods = np.linspace(bls_lo, bls_hi, 3000)

durations = np.linspace(
    0.01,
    min(0.3, TARGET_PERIOD * 0.2),
    10
)

print("Searching periods from", bls_lo, "to", bls_hi, "days")
print("Number of trial periods:", len(periods))
print("Durations:", durations)

bls_model = BoxLeastSquares(time_clean, flux_clean)

bls_result = bls_model.power(periods, durations)

best_index = np.argmax(bls_result.power)

best_period = float(np.asarray(bls_result.period)[best_index])
best_duration = float(np.asarray(bls_result.duration)[best_index])
best_t0 = float(np.asarray(bls_result.transit_time)[best_index])
best_power = float(np.asarray(bls_result.power)[best_index])

print("BLS found")
print("---------")
print("Best period:", best_period, "days")
print("NASA period:", TARGET_PERIOD, "days")
print("Best duration:", best_duration * 24, "hours")
print("Best transit time t0:", best_t0)
print("BLS power:", best_power)

period_error = abs(best_period - TARGET_PERIOD) / TARGET_PERIOD * 100
print("Period error:", period_error, "%")
</code></pre>
<p>输出：</p>
<pre><code>Searching periods from 1.116 to 2.073 days
Number of trial periods: 3000
Durations: [0.01 0.042 0.074 ... 0.3]
BLS found
---------
Best period: 1.5945858462866953 days
NASA period: 1.594745374 days
Best duration: 1.776 hours
Best transit time t0: 170.653
BLS power: 0.0111
Period error: 0.01%
</code></pre>
<p>BLS 找到的周期（1.5946 天）与 NASA 目录值（1.5947 天）<strong>仅差 0.01%</strong>。<code>bls_model.power()</code> 返回 BLS 功率谱——数值越高表示该周期的信号越显著。</p>
<h2>5.3 BLS 周期图</h2>
<pre><code>plt.figure(figsize=(10, 4))

plt.plot(bls_result.period, bls_result.power)

plt.axvline(
    TARGET_PERIOD,
    linestyle="--",
    alpha=0.8,
    label=f"NASA period = {TARGET_PERIOD:.4f} d"
)

plt.axvline(
    best_period,
    linestyle="-",
    alpha=0.8,
    label=f"BLS period = {best_period:.4f} d"
)

plt.xlabel("Trial period [days]")
plt.ylabel("BLS power")
plt.title(f"BLS periodogram: {TARGET_NAME}")
plt.legend()
plt.grid(True)

plt.show()
</code></pre>
<p><img src="9.webp" alt="" /></p>
<p>BLS 功率谱在正确周期处出现明显峰值，两条垂直线几乎完全重合。</p>
<hr />
<h1>6. 相位折叠光变曲线与行星参数估算</h1>
<h2>6.1 用最佳周期折叠</h2>
<pre><code>phase = compute_phase(time_clean, best_period, best_t0)

# Move phase so transit is near 0
phase_centered = ((time_clean - best_t0 + 0.5 * best_period) % best_period) / best_period - 0.5

plt.figure(figsize=(10, 4))

plt.scatter(phase_centered, flux_clean, s=4, alpha=0.35)

plt.axvline(-best_duration / (2 * best_period), color="red", linestyle="--", alpha=0.6)
plt.axvline(best_duration / (2 * best_period), color="red", linestyle="--", alpha=0.6)

plt.xlabel("Phase")
plt.ylabel("Normalized brightness")
plt.title(f"Phase-folded light curve at BLS period: {best_period:.5f} days")
plt.grid(True)

plt.show()
</code></pre>
<p><img src="10.webp" alt="" /></p>
<p>两根红色虚线标记了 BLS 估算的凌星持续时间区间。凌星信号在相位中心（0 附近）清晰可见。</p>
<h2>6.2 估算行星半径</h2>
<p>凌星深度与行星-恒星半径比的关系为：</p>
<p>$$\text{depth} = \left(\frac{R_p}{R_*}\right)^2$$</p>
<p>因此 $R_p / R_* = \sqrt{\text{depth}}$。若假设恒星半径等于太阳半径（109 地球半径），则行星半径 = 109 × $\sqrt{\text{depth}}$：</p>
<pre><code>in_transit = np.abs(phase_centered) &lt; best_duration / (2 * best_period)

f_in = np.nanmedian(flux_clean[in_transit])
f_out = np.nanmedian(flux_clean[~in_transit])

depth = (f_out - f_in) / f_out

radius_ratio = np.sqrt(max(depth, 0))

# Sun radius is about 109 Earth radii.
planet_radius_earth = radius_ratio * 109.0

print("Estimated transit physics")
print("-------------------------")
print("F_out:", f_out)
print("F_in:", f_in)
print("Depth:", depth)
print("Depth [%]:", depth * 100)
print("Rp / Rstar:", radius_ratio)
print("Planet radius if star is Sun-like:", planet_radius_earth, "Earth radii")
</code></pre>
<p>输出：</p>
<pre><code>Estimated transit physics
-------------------------
F_out: 1.0000432
F_in: 0.98740995
Depth: 0.01263266
Depth [%]: 1.263266
Rp / Rstar: 0.11239511
Planet radius if star is Sun-like: 12.25 Earth radii
</code></pre>
<p>估算行星半径约 12.25 地球半径，这是一个<strong>木星级</strong>大小的行星。如果将太阳替换为主序星，该半径与热木星（Hot Jupiter）的特征一致——超短周期（1.59 天）+ 大半径。</p>
<hr />
<h1>7. 本节小结</h1>
<p>本节完成了以下内容：</p>
<ul>
<li>引入了**残差平方和（SSE）**作为量化模型优劣的指标，解释了为何残差直接相加不可靠（正负抵消）。</li>
<li>通过遍历搜索实现了自动最优模型选取，核心思路与 BLS 算法一脉相承。</li>
<li>使用 <code>lightkurve</code> 从 MAST 数据库下载了真实 Kepler 数据，通过 NASA Exoplanet Archive 的 SQL 查询获取了已知行星信息。</li>
<li>实现了<strong>相位折叠</strong>——利用取模运算将所有数据折叠到一个周期内。</li>
<li>使用 **BLS（盒子最小二乘）**算法自动搜索周期，结果与 NASA 目录值仅差 0.01%。</li>
<li>从凌星深度估算行星半径（~12 地球半径，木星级），完成了从光变曲线到行星物理参数的完整分析链路。</li>
</ul>
<hr />
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="信息学"/>
  </entry>
  <entry>
    <title>国际夏令营｜第二天的见闻</title>
    <link href="https://luq-blog.xyz/posts/2026-07-08-sco-ysc-diary-04/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-07-08-sco-ysc-diary-04/</id>
    <published>2026-07-08T00:00:00.000Z</published>
    <updated>2026-07-08T00:00:00.000Z</updated>
    <summary>记录第一次参加国际夏令营的经历。</summary>
    <content type="html"><![CDATA[<h1>1. 一些必做任务－_－b</h1>
<p>emmm...今天下午开了个会，老师让我们每天写点东西。所以，后续这个系列的第一个一级标题都用来写<strong>今天必须写</strong>的东西吧。</p>
<h2>1.1 长期目标</h2>
<ol>
<li>
<p>我要在这里提升英语口语！这好像是一个不得不完成的任务吧，哈哈。</p>
</li>
<li>
<p>回国之后，我要发朋友圈（超大声）！！！这些天好难受啊，没法跟同学分（xuan）享（yao）这些天的日常。回去之后，我要让他们都知道(≧∇≦)ﾉ</p>
</li>
<li>
<p>如果可以的话，我想把这些天敲的代码放到 GitHub 上。这可能是我目前写代码最多的几天了。</p>
</li>
</ol>
<p><s>虽然没人会看我的代码，哈哈</s></p>
<h2>1.2 明日目标</h2>
<p>我明天真的好想完整地和我们 <strong>Astro-data science</strong> 的老师进行一次对话啊。每次都没法完全理解他的意思，他也不理解我的意思，好尴尬{{{(&gt;_&lt;)}}}</p>
<hr />
<h1>2. 真正的文章从这里开始</h1>
<h2>2.1 和昨天一样的事</h2>
<p>早上我们还是正常做早操，吃早饭、午饭、晚饭，并且上午上课，下午写作业，晚上写博客并复习代码。（别笑，天文学是唯一不能做实验的自然科学=￣ω￣=）</p>
<p>这里随便放几张今天的食物的照片好了:</p>
<blockquote>
<p>早上我们吃了饺子，但是土豆泥馅的￣へ￣除了奇怪没什么（忘带手机了，图片略）</p>
<p>中午我们吃了......午饭</p>
<p><img src="1.webp" alt="" /></p>
<p>晚上我们吃了......晚饭（</p>
<p><img src="2.webp" alt="" /></p>
</blockquote>
<h2>2.2 你问我课题上有什么进展！？我也不知道（</h2>
<p>其实还可以吧，我已经逐渐可以听懂老师讲的课啦(￣▽￣)同时也可以和团队里的其他人交流了。</p>
<p><s>虽然是全障碍交流，哈哈</s></p>
<p>课程的开始部分讲了如何看论文，就是告诉我们论文的每一部分都在讲什么。</p>
<p>此外，我们今天商量了每个人在团队里的分工。我可能是负责“Data analyze”的。至于为什么说是“可能”，当然是因为我也不确定我听到的是什么啊！！！</p>
<p>代码上，我们学习了如何计算拟合凌日时行星亮度曲线的模型，并计算这种模型的误差。</p>
<p>这里是代码：&lt;a href="/posts/2026-07-08-astro-data-science-02-period-detective/"&gt;笔记｜天文数据科学02｜周期侦探&lt;/a&gt;</p>
<h2>2.3 我也要看论文吗？</h2>
<p>今天的作业是这样的：</p>
<blockquote>
<p>Everyone analyses one scientific article (primary literature) relevant to your research stream.Each person must read a different article. The aim is to practise reading scientific language,interpreting data, and forming your own judgement, the same skills you will use when you begin writing your own papers.</p>
<p>Primary literature is a publication that reports the results of original scientific research for thefirst time. It forms the foundation of scientific knowledge and is where researchers begin when they set out to gather new data or test a hypothesis.</p>
</blockquote>
<p>翻译一下：</p>
<blockquote>
<p>每人分析一篇与自身研究方向相关的科学论文（原始研究文献），且所有人研读的论文不得重复。该任务旨在锻炼大家阅读专业科研文本、解读实验数据以及形成独立判断的能力，这些也是后续撰写个人论文时需要运用的核心技能。</p>
<p>一次文献是首次报道原创科学研究成果的出版物。它构成科学知识的基础，也是研究人员着手收集新数据或验证假设时的起点。</p>
</blockquote>
<p>所以啊，我们今天下午就在努力完成这项作业：</p>
<blockquote>
<p><img src="3.webp" alt="" /></p>
</blockquote>
<p>然后！写完这500个单词的作文后，我们还要写代码作业！！！为什么我们这个课题的就要这么卷啊！</p>
<h2>2.4 （异世界の庆典）</h2>
<p>我没听明白今天是否是这里重要的节日，但是总之今天下午这里举办了一场盛大的活动。</p>
<p>我们观看了他们的传统表演，体验了好多好玩的项目，还品尝了这里的传统美食。</p>
<p>但我想说的是，我射箭脱靶了，而且后面去拍照片，没有玩到其他几个活动啊〒▽〒</p>
<hr />
<h1>3.假装这里是总结</h1>
<p>好了，就写到这里吧。</p>
<blockquote>
<p>Tomorrow is another day!</p>
</blockquote>
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="思录"/>
  </entry>
  <entry>
    <title>天文数据科学｜光变曲线侦探</title>
    <link href="https://luq-blog.xyz/posts/2026-07-07-astro-data-science-01-light-curve-detective/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-07-07-astro-data-science-01-light-curve-detective/</id>
    <published>2026-07-07T00:00:00.000Z</published>
    <updated>2026-07-08T00:00:00.000Z</updated>
    <summary>本文通过分析神秘光变曲线，介绍系外行星探测的基本方法——凌星法。从数据清洗、归一化到手动识别凌星信号，逐步掌握天文数据处理的核心技能。</summary>
    <content type="html"><![CDATA[<p>恒星亮度的变化曲线被称为<strong>光变曲线（Light Curve）</strong>。当系外行星从恒星前方经过时，恒星亮度会出现微小的周期性下降——这就是**凌星法（Transit Method）**探测系外行星的基本原理。</p>
<p>本文是"Coursera系外行星探测"课程的课后作业，任务名称：<strong>Homework 1 — Light Curve Detective</strong>。通过分析一条神秘的光变曲线，学习如何清洗天文数据、识别异常点和估算凌星参数。目标不是证明发现了行星，而是<strong>仔细描述数据</strong>。</p>
<hr />
<h1>1. 作业说明与配置</h1>
<h2>1.1 作业目标</h2>
<p>以下是 notebook 中的原始英文说明：</p>
<blockquote>
<p>Homework 1 — Light Curve Detective</p>
<p>In Day One you learned how to use a Jupyter notebook, Python variables, lists, NumPy arrays, and plots to study a star's brightness.</p>
<p>In this homework, you will repeat those skills and analyze a <strong>mystery light curve</strong>.</p>
<p>Your goal is not to prove that you found a planet. Your goal is to describe the data carefully.</p>
</blockquote>
<p>本次作业的核心在于复现课堂上学到的技能（Jupyter Notebook、Python 变量、列表、NumPy 数组和 Matplotlib 绘图），并用它们来分析一条<strong>神秘的光变曲线</strong>。</p>
<h2>1.2 作业格式说明</h2>
<blockquote>
<p>How to work with this notebook</p>
<p>This homework is written in a Coursera-style format.</p>
<p>Some cells have this pattern:</p>
<pre><code>### START CODE HERE ###
# your code
### END CODE HERE ###
</code></pre>
<p>Replace <code>None</code> with your own code. After many exercises, there is a small test cell using <code>assert</code>.</p>
<p>If a test fails, read the error message, fix your code, and run the cell again.</p>
</blockquote>
<p>作业采用 Coursera 风格，部分代码单元格内标记了 <code>### START CODE HERE ###</code> 和 <code>### END CODE HERE ###</code> 区间，需要将 <code>None</code> 替换为自己的代码。每个练习后面都有一个使用 <code>assert</code>（断言）的测试单元格，如果测试失败，需要阅读错误信息、修正代码后重新运行。</p>
<h2>1.3 环境配置（Part 0 — Setup）</h2>
<blockquote>
<p>Part 0 — Setup</p>
<p>Run this cell first. It imports the tools we need today.</p>
<p>We only use public Python packages that are already available in Google Colab: NumPy and Matplotlib.</p>
</blockquote>
<p>首先运行此单元格导入 NumPy（数值计算库）和 Matplotlib（绘图库），这两个是 Google Colab 中预装的公开 Python 包：</p>
<pre><code>import numpy as np
import matplotlib.pyplot as plt

print("Setup complete.")
</code></pre>
<p>输出：</p>
<pre><code>Setup complete.
</code></pre>
<hr />
<h1>2. 热身复习（Part 1 — Warm-up review）</h1>
<blockquote>
<p>Part 1 — Warm-up review from today's class</p>
<p>Before we analyze a mystery star, let's repeat the basic skills from today's lesson.</p>
</blockquote>
<h2>2.1 练习 1：自我介绍</h2>
<pre><code># Exercise 1 — Hello, Exoplanet Hunter
# Replace None with your own name and team number.

### START CODE HERE ###
student_name = 'Luquiescene'
team_number = 1
### END CODE HERE ###

print("Hello, Exoplanet Hunter!")
print("Student:", student_name)
print("Team:", team_number)
</code></pre>
<p>输出：</p>
<pre><code>Hello, Exoplanet Hunter!
Student: Luquiescene
Team: 1
</code></pre>
<p>测试单元格使用 <code>assert</code> 验证变量类型是否正确：</p>
<pre><code># Test 1
assert isinstance(student_name, str), "student_name should be a string, for example: 'Aigerim'"
assert student_name.strip() != "", "student_name should not be empty."
assert isinstance(team_number, int), "team_number should be an integer, for example: 1"
print("Test 1 passed.")
</code></pre>
<p>输出：</p>
<pre><code>Test 1 passed.
</code></pre>
<p><code>isinstance(student_name, str)</code> 检查 <code>student_name</code> 是否为字符串类型，<code>str.strip() != ""</code> 确保字符串非空。</p>
<h2>2.2 练习 2：亮度下降计算</h2>
<p>当行星凌星时，恒星亮度会从正常值小幅下降。设正常亮度为 1.00，凌星期亮度为 0.985，计算下降量：</p>
<pre><code># Exercise 2 — brightness drop
# A star has normal brightness 1.00.
# During a possible transit, the brightness becomes 0.985.
# Calculate the drop in brightness.

normal_brightness = 1.00
during_transit = 0.985

### START CODE HERE ###
dip = normal_brightness - during_transit
dip_percent = dip * 100
### END CODE HERE ###

print("Brightness drop:", dip)
print("Brightness drop in percent:", dip_percent)
</code></pre>
<p>输出：</p>
<pre><code>Brightness drop: 0.015000000000000013
Brightness drop in percent: 1.5000000000000013
</code></pre>
<p><code>dip</code> 是亮度下降的分数值，乘以 100 后得到百分比。注意浮点数运算存在微小精度误差（0.015 vs 0.015000000000000013），测试中允许了 <code>1e-12</code> 的容差：</p>
<pre><code># Test 2
assert abs(dip - 0.015) &lt; 1e-12, "Check: dip = normal_brightness - during_transit"
assert abs(dip_percent - 1.5) &lt; 1e-12, "Check: dip_percent = dip * 100"
print("Test 2 passed.")
</code></pre>
<p>输出：</p>
<pre><code>Test 2 passed.
</code></pre>
<h2>2.3 练习 3：列表基本操作</h2>
<p>用 Python 原生列表存储一条小型光变曲线的时间与亮度数据，练习基本的数据量测：</p>
<pre><code># Exercise 3 — lists and simple measurements
# This is a very small fake light curve.

time_small = [1., 2., 3., 4., 5., 6., 7.]
brightness_small = [1.00, 1.00, 0.99, 0.96, 0.95, 0.96, 0.99]

### START CODE HERE ###
n_time_points = len(time_small)
minimum_brightness = min(brightness_small)
maximum_brightness = max(brightness_small)
### END CODE HERE ###

print("Number of points:", n_time_points)
print("Minimum brightness:", minimum_brightness)
print("Maximum brightness:", maximum_brightness)
</code></pre>
<p>输出：</p>
<pre><code>Number of points: 7
Minimum brightness: 0.95
Maximum brightness: 1.0
</code></pre>
<p><code>len()</code> 返回列表中的元素个数，<code>min()</code> 和 <code>max()</code> 分别获取最小值和最大值。7个数据点中亮度的最低值为 0.95，对应可能的凌星信号：</p>
<pre><code># Test 3
assert n_time_points == 7, "Use len(time_small)."
assert abs(minimum_brightness - 0.95) &lt; 1e-12, "Use min(brightness_small)."
assert abs(maximum_brightness - 1.00) &lt; 1e-12, "Use max(brightness_small)."
print("Test 3 passed.")
</code></pre>
<p>输出：</p>
<pre><code>Test 3 passed.
</code></pre>
<h2>2.4 练习 4：绘制小型光变曲线</h2>
<p><code>plt.plot(..., marker="o")</code> 用带圆形标记的折线图可视化光变曲线：</p>
<pre><code># Exercise 4 — plot the small light curve
# Use plt.plot(..., marker="o").

plt.figure(figsize=(7, 4))

### START CODE HERE ###
plt.plot(time_small, brightness_small, marker="o")
### END CODE HERE ###

plt.xlabel("Time [days]")
plt.ylabel("Brightness")
plt.title("Small light curve")
plt.grid(True)
plt.show()
</code></pre>
<p><img src="1.webp" alt="" /></p>
<blockquote>
<h3>Quick check</h3>
<p>In a light curve, a possible transit appears as a small drop in brightness.</p>
<p>But real data are not perfectly smooth. They can contain noise, missing values, and outliers.</p>
</blockquote>
<p>光变曲线中，潜在凌星表现为<strong>小幅亮度下降</strong>。但真实数据并不完美平滑——它们可能包含<strong>噪声、缺失值（NaN）和异常值（Outlier）</strong>。</p>
<h2>2.5 练习 5：NumPy 时间网格与噪声模拟</h2>
<p>真实观测数据不像上面的小型光变曲线那样平滑。需要使用 NumPy 创建高密度时间网格，并叠加随机噪声来模拟真实场景。</p>
<p><code>np.linspace(start, stop, number_of_points)</code> 在起止区间内均匀生成指定数量的点。<code>rng.normal(mean, std, size)</code> 从正态分布中抽样：</p>
<pre><code># Exercise 5 — NumPy time grid and noisy brightness
# Today you used NumPy to create many points.
# np.linspace(start, stop, number_of_points) creates an evenly spaced grid.

rng = np.random.default_rng(42)

### START CODE HERE ###
time_demo = np.linspace(0, 10, 300)
noise = rng.normal(0, 0.01, len(time_demo))
brightness_demo = 1 + noise
### END CODE HERE ###

print("First 5 time values:", time_demo[:5])
print("First 5 brightness values:", brightness_demo[:5])
</code></pre>
<p>输出：</p>
<pre><code>First 5 time values: [0.         0.03344482 0.06688963 0.10033445 0.13377926]
First 5 brightness values: [1.00304717 0.98960016 1.00750451 1.00940565 0.98048965]
</code></pre>
<p><code>np.random.default_rng(42)</code> 创建了一个随机数生成器，种子值 42 保证每次运行生成相同的随机序列。<code>rng.normal(0, 0.01, 300)</code> 生成 300 个均值为 0、标准差为 0.01 的正态分布噪声值。正常亮度 1.0 叠加噪声后，亮度在 1.0 ± 0.03 范围内波动：</p>
<pre><code># Test 5
assert isinstance(time_demo, np.ndarray), "time_demo should be a NumPy array."
assert isinstance(brightness_demo, np.ndarray), "brightness_demo should be a NumPy array."
assert len(time_demo) == 300, "time_demo should contain 300 points."
assert len(brightness_demo) == 300, "brightness_demo should contain 300 points."
print("Test 5 passed.")
</code></pre>
<p>输出：</p>
<pre><code>Test 5 passed.
</code></pre>
<p>用 <code>plt.scatter()</code> 散点图绘制含噪声的光变曲线，散点图比折线图更适合展示离散观测数据：</p>
<pre><code># Plot your noisy flat light curve.

plt.figure(figsize=(8, 4))
plt.scatter(time_demo, brightness_demo, s=8, alpha=0.7)
plt.xlabel("Time [days]")
plt.ylabel("Brightness")
plt.title("Noisy brightness measurements")
plt.grid(True)
plt.show()
</code></pre>
<p><img src="2.webp" alt="" /></p>
<p><code>s=8</code> 设置散点大小，<code>alpha=0.7</code> 设置透明度使重叠点可见。</p>
<h2>2.6 练习 6：模拟人为凌星</h2>
<p>利用布尔掩码在指定时间段内降低亮度，模拟一次凌星事件。<code>(time_demo &gt; 4) &amp; (time_demo &lt; 5)</code> 是一个布尔数组，<code>True</code> 表示该数据点位于第 4 天到第 5 天之间：</p>
<pre><code># Exercise 6 — create a simple synthetic transit
# We will make brightness lower between day 4 and day 5.

brightness_transit = brightness_demo.copy()

### START CODE HERE ###
in_transit = (time_demo &gt; 4) &amp; (time_demo &lt; 5)
brightness_transit[in_transit] = 0
### END CODE HERE ###

print("Number of points inside the transit:", np.sum(in_transit))
</code></pre>
<p>输出：</p>
<pre><code>Number of points inside the transit: 30
</code></pre>
<p><code>.copy()</code> 创建数组的独立副本，避免修改原数组。<code>np.sum(in_transit)</code> 对布尔数组求和，<code>True</code> 计为 1、<code>False</code> 计为 0，因此得到凌星区间内的数据点数（30 个）：</p>
<pre><code># Test 6
assert in_transit.dtype == bool, "in_transit should be a Boolean mask."
assert np.sum(in_transit) &gt; 0, "Your transit mask should select some points."
assert np.nanmin(brightness_transit) &lt; np.nanmin(brightness_demo), "The transit should make the brightness lower."
print("Test 6 passed.")
</code></pre>
<p>输出：</p>
<pre><code>Test 6 passed.
</code></pre>
<p><code>np.nanmin()</code> 计算忽略 NaN 的最小值，验证凌星确实降低了亮度。绘制人为制造的凌星光变曲线：</p>
<pre><code># Plot the synthetic transit.

plt.figure(figsize=(8, 4))
plt.scatter(time_demo, brightness_transit, s=8, alpha=0.7)
plt.xlabel("Time [days]")
plt.ylabel("Brightness")
plt.title("Synthetic transit")
plt.grid(True)
plt.show()
</code></pre>
<p><img src="3.webp" alt="" /></p>
<p>图中在第 4 到第 5 天之间出现了一个清晰的"凹陷"，亮度从正常的 ∼1.0 下降到 0。</p>
<hr />
<h1>3. 神秘光变曲线（Part 2 — Your mystery star）</h1>
<blockquote>
<p>Part 2 — Your mystery star</p>
<p>Now you will receive a mystery light curve.</p>
<p>The data are synthetic, but they are designed to look a little like real telescope data:</p>
<ul>
<li>there is noise;</li>
<li>there are a few missing values;</li>
<li>there are a few outliers;</li>
<li>there may be a repeating transit-like dip.</li>
</ul>
<p>Do not worry if you do not understand every line in the generator function. Your job is to analyze the data it creates.</p>
</blockquote>
<p>接下来我们将接收一条神秘的光变曲线。数据是<strong>人工合成</strong>的，但模拟了真实望远镜观测的特点：含有测量噪声、缺失值（NaN）、异常亮斑，以及可能存在的周期性凌星信号。</p>
<h2>3.1 数据生成函数</h2>
<p>以下函数根据队伍编号和学生编号生成可复现的光变曲线，内部原理包括：</p>
<ul>
<li>用<strong>固定随机种子</strong>（<code>seed = 1000 + 100 * team + student_id</code>）保证同一队伍同一学生始终得到相同的曲线</li>
<li>从 5 个预设周期（3.25、4.10、5.60、6.80、8.40 天）中分配一个周期</li>
<li>用 <code>np.sin</code> 添加长周期亮度趋势（缓慢变化）</li>
<li>用取模运算 <code>((time - t0 + P/2) % P) - P/2</code> 计算每个时间点到最近凌星中心的距离，实现<strong>周期性凹陷</strong></li>
<li>随机选择 8 个点设为 NaN、10 个点添加亮斑异常值</li>
</ul>
<pre><code>def make_mystery_light_curve(team, student_id, n_days=45, cadence_minutes=30):
    # This function creates one reproducible mystery light curve.
    # Same team + same student_id = same target.
    seed = 1000 + 100 * int(team) + int(student_id)
    rng = np.random.default_rng(seed)

    time = np.arange(0, n_days, cadence_minutes / (60 * 24))

    possible_periods = np.array([3.25, 4.10, 5.60, 6.80, 8.40])
    period = possible_periods[(int(team) + int(student_id)) % len(possible_periods)]
    period = period + rng.normal(0, 0.04)

    first_transit_time = rng.uniform(1.0, period + 0.5)
    duration = rng.uniform(0.14, 0.28)
    depth = rng.uniform(0.010, 0.026)

    slow_trend = 1 + 0.005 * np.sin(2 * np.pi * time / 22 + rng.uniform(0, 2 * np.pi))
    flux = slow_trend.copy()

    # Distance from the nearest transit center.
    dt = ((time - first_transit_time + period / 2) % period) - period / 2
    transit_mask = np.abs(dt) &lt; duration / 2
    flux[transit_mask] -= depth

    # Add random measurement noise.
    flux += rng.normal(0, 0.0025, size=len(time))

    # Add missing values.
    nan_index = rng.choice(len(time), size=8, replace=False)
    flux[nan_index] = np.nan

    # Add a few bright outliers.
    finite_index = np.where(np.isfinite(flux))[0]
    outlier_index = rng.choice(finite_index, size=10, replace=False)
    flux[outlier_index] += rng.uniform(0.035, 0.070, size=len(outlier_index))

    target_id = f"MYSTERY-{int(team):02d}-{int(student_id):02d}"

    truth = {
        "period_days": period,
        "first_transit_time_days": first_transit_time,
        "duration_days": duration,
        "depth_fraction": depth,
        "depth_percent": depth * 100,
    }

    return {
        "target_id": target_id,
        "time": time,
        "flux": flux,
        "truth": truth,
    }
</code></pre>
<p><code>np.arange(0, n_days, step)</code> 等价于 Python 的 <code>range</code>，但返回 NumPy 数组。<code>cadence_minutes / (60 * 24)</code> 将分钟间隔转换为天数步长（30分钟 = 0.02083 天）。<code>np.where(np.isfinite(flux))[0]</code> 返回所有有限值（非 NaN）的索引，<code>rng.choice(..., replace=False)</code> 从中无放回随机抽取异常值位置。</p>
<h2>3.2 练习 7：选取目标</h2>
<p><code>TEAM</code> 应为 1、2、3 或 4；<code>STUDENT_ID</code> 为 1 到 99 的任意整数：</p>
<pre><code># Exercise 7 — choose your target
# Use your real team number and your own student number.
# TEAM must be 1, 2, 3, or 4.
# STUDENT_ID can be any integer from 1 to 99.

### START CODE HERE ###
TEAM = 1
STUDENT_ID = 36
### END CODE HERE ###

MYSTERY_TARGET = make_mystery_light_curve(TEAM, STUDENT_ID)

time_raw = MYSTERY_TARGET["time"]
flux_raw = MYSTERY_TARGET["flux"]

print("Target ID:", MYSTERY_TARGET["target_id"])
print("Number of measurements:", len(time_raw))
</code></pre>
<p>输出：</p>
<pre><code>Target ID: MYSTERY-01-36
Number of measurements: 2160
</code></pre>
<p>45 天内以 30 分钟间隔采样，总共 2160 个数据点：</p>
<pre><code># Test 7
assert TEAM in [1, 2, 3, 4], "TEAM must be 1, 2, 3, or 4."
assert isinstance(STUDENT_ID, int), "STUDENT_ID should be an integer."
assert 1 &lt;= STUDENT_ID &lt;= 99, "STUDENT_ID should be between 1 and 99."
assert len(time_raw) == len(flux_raw), "time and flux should have the same length."
print("Test 7 passed.")
</code></pre>
<p>输出：</p>
<pre><code>Test 7 passed.
</code></pre>
<hr />
<h1>4. 绘制原始光变曲线（Part 3 — Look at the raw light curve）</h1>
<blockquote>
<p>Part 3 — Look at the raw light curve</p>
<p>First, plot the data exactly as the telescope gives it to us.</p>
<p>At this stage, do not clean anything yet. Just look.</p>
</blockquote>
<p>在清洗之前，直接绘制望远镜接收到的原始数据，"看看数据长什么样"：</p>
<h2>4.1 练习 8：原始光变曲线</h2>
<pre><code># Exercise 8 — plot raw mystery light curve

plt.figure(figsize=(10, 4))

### START CODE HERE ###
plt.scatter(time_raw, flux_raw, s=4, alpha=0.6)
### END CODE HERE ###

plt.xlabel("Time [days]")
plt.ylabel("Raw flux")
plt.title("Raw mystery light curve")
plt.grid(True)
plt.show()
</code></pre>
<p><img src="4.webp" alt="" /></p>
<h2>4.2 观察 1（Observation 1）</h2>
<blockquote>
<p>Observation 1</p>
<p>Write 2–3 sentences below:</p>
<ul>
<li>Does the star look constant or variable?</li>
<li>Do you see any strange points?</li>
<li>Do you see any possible dips?</li>
</ul>
<p><strong>Your answer:</strong></p>
<p>The star's brightness stays roughly steady at 1.00, making it a constant-brightness star.</p>
<p>There are outlier bright points visible in the plot.</p>
<p>Clusters of data points dip downward at regular time intervals, which are characteristic brightness dips caused by planetary transits.</p>
</blockquote>
<p>恒星亮度大致稳定在 1.00 附近，属于亮度恒定型恒星。图像中存在若干异常亮斑（outlier），高出正常值很多。在多个位置可以看到<strong>成簇的数据点向下凹陷</strong>，这是行星凌星的典型亮度特征。</p>
<hr />
<h1>5. 数据清洗与归一化（Part 4 — Clean and normalize）</h1>
<blockquote>
<p>Part 4 — Clean and normalize</p>
<p>Real light curves often contain missing values and outliers.</p>
<p>We will do three simple steps:</p>
<ol>
<li>remove missing values (<code>NaN</code>);</li>
<li>remove very strange bright outliers;</li>
<li>normalize the flux so that the typical brightness is close to 1.</li>
</ol>
</blockquote>
<p>真实光变曲线常含有缺失值和异常值，需要进行三步清洗：去除 NaN → 去除异常亮斑 → 归一化流量。</p>
<h2>5.1 练习 9：清洗与归一化</h2>
<pre><code># Exercise 9 — remove NaNs, remove bright outliers, normalize

### START CODE HERE ###
finite_mask = np.isfinite(flux_raw)

time_no_nan = time_raw[finite_mask]
flux_no_nan = flux_raw[finite_mask]

median_flux = np.median(flux_no_nan)
scatter_flux = 1.4826 * np.median(np.abs(flux_no_nan - median_flux))

outlier_mask = flux_no_nan &gt; median_flux + 5 * scatter_flux

time_clean = time_no_nan[~outlier_mask]
flux_clean = flux_no_nan[~outlier_mask]

flux_normalized = flux_clean / median_flux
### END CODE HERE ###

print("Raw points:", len(time_raw))
print("After cleaning:", len(time_clean))
print("Median normalized flux:", np.nanmedian(flux_normalized))
</code></pre>
<p>输出：</p>
<pre><code>Raw points: 2160
After cleaning: 2142
Median normalized flux: 0.999955187521236
</code></pre>
<p><strong>逐步解析：</strong></p>
<ol>
<li><code>np.isfinite(flux_raw)</code>：判断每个值是否为有限值（排除 <code>NaN</code> 和 <code>inf</code>），返回布尔掩码。用此掩码筛选出有效数据。</li>
<li><code>np.median(flux_no_nan)</code>：计算中位数，作为典型亮度值。中位数比均值更稳健，不受异常值影响。</li>
<li><strong>MAD 异常值检测</strong>：<code>scatter_flux = 1.4826 * np.median(np.abs(flux_no_nan - median_flux))</code> 是**中位数绝对偏差（MAD）**乘以缩放因子 1.4826，使其与正态分布的标准差等价。将超出中位数 5 倍 MAD 的点标记为异常值。</li>
<li><code>~outlier_mask</code>：<code>~</code> 运算符对布尔数组取反，保留非异常值的数据点。</li>
<li><code>flux_clean / median_flux</code>：将整条光变曲线除以中位数，使典型亮度归一化到 1.0 附近。</li>
</ol>
<p>原始 2160 个点 → 去除 8 个 NaN → 去除 10 个异常亮斑 → 剩余 2142 个有效数据点：</p>
<pre><code># Test 9
assert np.all(np.isfinite(time_clean)), "time_clean should not contain NaNs."
assert np.all(np.isfinite(flux_normalized)), "flux_normalized should not contain NaNs."
assert len(time_clean) == len(flux_normalized), "time_clean and flux_normalized should have the same length."
assert abs(np.nanmedian(flux_normalized) - 1.0) &lt; 0.01, "The median normalized flux should be close to 1."
print("Test 9 passed.")
</code></pre>
<p>输出：</p>
<pre><code>Test 9 passed.
</code></pre>
<h2>5.2 练习 10：绘制清洗后的光变曲线</h2>
<pre><code># Exercise 10 — plot cleaned and normalized light curve

plt.figure(figsize=(10, 4))

### START CODE HERE ###
plt.scatter(time_clean, flux_normalized, s=4, alpha=0.6)
### END CODE HERE ###

plt.xlabel("Time [days]")
plt.ylabel("Normalized flux")
plt.title("Cleaned and normalized light curve")
plt.grid(True)
plt.show()
</code></pre>
<p><img src="5.webp" alt="" /></p>
<h2>5.3 观察 2（Observation 2）</h2>
<blockquote>
<p>Observation 2</p>
<p>Compare the raw plot and the cleaned plot.</p>
<p>Write 2–3 sentences:</p>
<ul>
<li>What changed after cleaning?</li>
<li>Is it easier to see possible dips now?</li>
<li>Why is normalization useful?</li>
</ul>
<p><strong>Your answer:</strong></p>
<p>After cleaning, the bright outliers and NaN are removed, making the light curve look smoother.</p>
<p>Now it is easier to see possible dips.</p>
<p>The normalization makes it much easier to identify dips below the baseline.</p>
</blockquote>
<p>清洗后，亮斑和 NaN 被移除，光变曲线更平滑。凌星凹陷更加清晰可见，更容易识别低于基线（1.0）的数据点簇。归一化的价值在于将流量统一到 1.0 附近，使得不同目标的亮度变化可以直接比较。</p>
<hr />
<h1>6. 手动凌星探测（Part 5 — Manual transit detective）</h1>
<blockquote>
<p>Part 5 — Manual transit detective</p>
<p>Now we will estimate a possible transit by hand.</p>
<p>This is not a final discovery. It is a first human inspection.</p>
<p>A possible transit should look like a short drop in normalized flux.</p>
</blockquote>
<p>这一步是<strong>人工初步检查</strong>，不是最终确认，而是用肉眼辅助代码来估算可能的凌星参数。一个可能的凌星信号应该在归一化流量中表现为<strong>短暂的凹陷</strong>。</p>
<h2>6.1 练习 11：找到最深点</h2>
<p><code>np.argmin(array)</code> 返回数组中最小值的<strong>索引位置</strong>（而非最小值本身），用于定位光变曲线中亮度最低的时刻：</p>
<pre><code># Exercise 11 — find the deepest point
# Hint: np.argmin(array) gives the index of the smallest value.

### START CODE HERE ###
min_index = np.argmin(flux_normalized)
transit_time_guess = time_clean[min_index]
minimum_flux = flux_normalized[min_index]
transit_depth_fraction = 1.0 - minimum_flux
transit_depth_percent = transit_depth_fraction * 100
### END CODE HERE ###

print("Deepest point time [days]:", transit_time_guess)
print("Minimum normalized flux:", minimum_flux)
print("Estimated depth [%]:", transit_depth_percent)
</code></pre>
<p>输出：</p>
<pre><code>Deepest point time [days]: 10.895833333333332
Minimum normalized flux: 0.9753378892244227
Estimated depth [%]: 2.466211077557734
</code></pre>
<p>最深点出现在第 10.90 天，最小归一化流量约 0.975，估算凌星深度约 2.47%。凌星深度 = 1.0 − 最小流量，表示恒星亮度因凌星而下降的比例：</p>
<pre><code># Test 11
assert isinstance(min_index, (int, np.integer)), "min_index should be an integer index."
assert np.isfinite(transit_time_guess), "transit_time_guess should be a number."
assert np.isfinite(transit_depth_percent), "transit_depth_percent should be a number."
assert transit_depth_percent &gt; 0, "The depth should be positive."
print("Test 11 passed.")
</code></pre>
<p>输出：</p>
<pre><code>Test 11 passed.
</code></pre>
<p>在整条光变曲线中用虚线标记最深点位置：</p>
<pre><code># Plot the cleaned light curve and mark the deepest point.

plt.figure(figsize=(10, 4))
plt.scatter(time_clean, flux_normalized, s=4, alpha=0.6)
plt.axvline(transit_time_guess, linestyle="--", label="deepest point")
plt.xlabel("Time [days]")
plt.ylabel("Normalized flux")
plt.title("Where is the deepest point?")
plt.legend()
plt.grid(True)
plt.show()
</code></pre>
<p><img src="6.webp" alt="" /></p>
<p><code>plt.axvline()</code> 在 x 轴指定位置绘制一条垂直的参考线，<code>linestyle="--"</code> 设置为虚线。</p>
<h2>6.2 练习 12：放大观察凌星区域</h2>
<p><code>np.abs(time_clean - transit_time_guess) &lt; window</code> 创建一个布尔掩码，选取最深点前后各 1.5 天范围内的数据：</p>
<pre><code># Exercise 12 — zoom near the deepest point
# We will look at a window of 1.5 days before and after the deepest point.

window = 1.5

### START CODE HERE ###
zoom_mask = np.abs(time_clean - transit_time_guess) &lt; window
### END CODE HERE ###

plt.figure(figsize=(8, 4))
plt.scatter(time_clean[zoom_mask], flux_normalized[zoom_mask], s=12, alpha=0.8)
plt.axvline(transit_time_guess, linestyle="--")
plt.xlabel("Time [days]")
plt.ylabel("Normalized flux")
plt.title("Zoom near the deepest point")
plt.grid(True)
plt.show()
</code></pre>
<p><img src="7.webp" alt="" /></p>
<p>将时间窗口外的数据过滤掉、仅保留窗口内的点，散点大小增大到 <code>s=12</code>：</p>
<pre><code># Test 12
assert zoom_mask.dtype == bool, "zoom_mask should be a Boolean mask."
assert np.sum(zoom_mask) &gt; 5, "Your zoom window should contain several data points."
print("Test 12 passed.")
</code></pre>
<p>输出：</p>
<pre><code>Test 12 passed.
</code></pre>
<h2>6.3 练习 13：估算凌星持续时间</h2>
<p>使用简单阈值法：取正常流量（1.0）与最低流量之间的中点作为阈值，低于该阈值的点视为凌星期间。<code>np.ptp()</code>（peak-to-peak）计算数组的最大值减最小值，用于估算凌星持续时长：</p>
<pre><code># Exercise 13 — estimate a rough duration
# We use a simple threshold halfway between normal flux (1.0) and the deepest point.
# Points below this threshold near the deepest point are counted as part of the dip.

### START CODE HERE ###
threshold = (1.0 + minimum_flux) / 2
near_transit_mask = zoom_mask
in_dip_mask = near_transit_mask &amp; (flux_normalized &lt; threshold)
estimated_duration_days = np.ptp(time_clean[in_dip_mask])
### END CODE HERE ###

print("Threshold:", threshold)
print("Estimated duration [days]:", estimated_duration_days)
print("Estimated duration [hours]:", estimated_duration_days * 24)
</code></pre>
<p>输出：</p>
<pre><code>Threshold: 0.9876689446122113
Estimated duration [days]: 0.20833333333333393
Estimated duration [hours]: 5.000000000000014
</code></pre>
<p><code>&amp;</code> 运算符组合两个布尔掩码：时间窗口内 AND 流量低于阈值。估算凌星持续时间约 0.21 天（约 5 小时）：</p>
<pre><code># Test 13
assert np.isfinite(threshold), "threshold should be a number."
assert in_dip_mask.dtype == bool, "in_dip_mask should be a Boolean mask."
assert np.sum(in_dip_mask) &gt;= 2, "You should have at least two points inside the possible dip."
assert estimated_duration_days &gt; 0, "estimated_duration_days should be positive."
print("Test 13 passed.")
</code></pre>
<p>输出：</p>
<pre><code>Test 13 passed.
</code></pre>
<h2>6.4 观察 3（Observation 3）</h2>
<blockquote>
<p>Observation 3</p>
<p>Look at your zoom plot.</p>
<p>Write 2–4 sentences:</p>
<ul>
<li>Does the deepest point look like part of a real dip?</li>
<li>Or does it look like one strange measurement?</li>
<li>What makes you confident or uncertain?</li>
</ul>
<p><strong>Your answer:</strong></p>
<p>Yes, the deepest point look like part of a real dip. Because many data points around it also lie below the baseline.</p>
</blockquote>
<p>最深点周围有多个数据点也低于基线，<strong>这是一个真实的凌星凹陷而非孤立的噪声点</strong>。</p>
<hr />
<h1>7. 寻找重复凌星信号（Part 6 — Look for repeated dips by eye）</h1>
<blockquote>
<p>Part 6 — Look for repeated dips by eye</p>
<p>A real planet should usually produce more than one dip.</p>
<p>In this section, we will not use an automatic search algorithm. We will only make a helpful plot and inspect it.</p>
</blockquote>
<p>真正的系外行星应产生<strong>多次</strong>凌星信号。本节不采用自动搜索算法，仅通过可视化来人工检查。</p>
<h2>7.1 练习 14：标记低流量点</h2>
<p>使用与前一步相同的阈值，标记整条光变曲线中所有低于阈值的点：</p>
<pre><code># Exercise 14 — highlight low-flux points
# Create a mask for points that are below the threshold.

### START CODE HERE ###
low_flux_mask = flux_normalized &lt; threshold
number_of_low_points = np.sum(low_flux_mask)
### END CODE HERE ###

print("Number of low-flux points:", number_of_low_points)
</code></pre>
<p>输出：</p>
<pre><code>Number of low-flux points: 66
</code></pre>
<p>66 个低流量点分布在 45 天的观测中：</p>
<pre><code># Test 14
assert low_flux_mask.dtype == bool, "low_flux_mask should be a Boolean mask."
assert number_of_low_points == np.sum(low_flux_mask), "number_of_low_points should count the True values."
assert number_of_low_points &gt; 0, "There should be at least one low-flux point."
print("Test 14 passed.")
</code></pre>
<p>输出：</p>
<pre><code>Test 14 passed.
</code></pre>
<p>用两种颜色区分所有点和低流量点，并画出阈值水平线：</p>
<pre><code># Plot all points and highlight the low-flux points.

plt.figure(figsize=(10, 4))
plt.scatter(time_clean, flux_normalized, s=4, alpha=0.35, label="all points")
plt.scatter(time_clean[low_flux_mask], flux_normalized[low_flux_mask],
            s=14, alpha=0.9, label="low-flux points")
plt.axhline(threshold, linestyle="--", label="threshold")
plt.xlabel("Time [days]")
plt.ylabel("Normalized flux")
plt.title("Low-flux points in the light curve")
plt.legend()
plt.grid(True)
plt.show()
</code></pre>
<p><img src="8.webp" alt="" /></p>
<p><code>plt.axhline()</code> 在 y 轴指定位置绘制水平参考线。<code>plt.legend()</code> 显示图例。</p>
<h2>7.2 观察 4（Observation 4）</h2>
<blockquote>
<p>Observation 4</p>
<p>Look at the highlighted points.</p>
<p>Write 2–4 sentences:</p>
<ul>
<li>Do low-flux points appear in several places?</li>
<li>Are they grouped into short dips?</li>
<li>Would you call this a possible transit-like signal? Why or why not?</li>
</ul>
<p><strong>Your answer:</strong></p>
<p>Yes, low-flux points appear in several places.</p>
<p>Yes, they grouped into short dips.</p>
<p>I would call this a possible transit-like signal.Because they appear with a constant period.</p>
</blockquote>
<p>低流量点确实出现在多个位置，以短簇形式聚集。它们以<strong>近乎固定的间隔重复出现</strong>，这符合周期性凌星信号的特征。因此可以将其称为<strong>可能的凌星信号</strong>（possible transit-like signal）。</p>
<hr />
<h1>8. 观测报告（Part 7 — Observation card）</h1>
<blockquote>
<p>Part 7 — Observation card</p>
<p>Now summarize your result like a scientist.</p>
<p>Use careful language. You are not confirming a planet. You are reporting a <strong>possible transit-like signal</strong>.</p>
</blockquote>
<h2>8.1 练习 15：填写观测卡片</h2>
<p>将之前手动估算的结果整理为一张观测卡片。使用审慎的科学语言——报告的是"<strong>可能的凌星信号</strong>"，而非"<strong>确认的行星</strong>"：</p>
<pre><code># Exercise 15 — observation card
# Fill this card using your measurements.

### START CODE HERE ###
observation_card = {
    "target_id": MYSTERY_TARGET["target_id"],
    "estimated_transit_time_days": transit_time_guess,
    "estimated_depth_percent": transit_depth_percent,
    "estimated_duration_hours": estimated_duration_days * 24,
    "number_of_low_flux_points": number_of_low_points,
    "confidence": "medium",
    "conclusion": "A possible transit-like signal was detected in the light curve. "
    "The dips are short, grouped, and appear at several locations. "
    "But further period-folding analysis is needed to verify whether "
    "the signal repeats at regular intervals.",
}
### END CODE HERE ###

observation_card
</code></pre>
<p>输出：</p>
<pre><code>{'target_id': 'MYSTERY-01-36',
 'estimated_transit_time_days': np.float64(10.895833333333332),
 'estimated_depth_percent': np.float64(2.466211077557734),
 'estimated_duration_hours': np.float64(5.000000000000014),
 'number_of_low_flux_points': np.int64(66),
 'confidence': 'medium',
 'conclusion': 'A possible transit-like signal was detected in the light curve. '
 'The dips are short, grouped, and appear at several locations. '
 'But further period-folding analysis is needed to verify whether '
 'the signal repeats at regular intervals.'}
</code></pre>
<p><code>confidence</code> 设为 <code>"medium"</code>（中等置信度），结论中使用了 <code>"possible transit-like signal"</code> 而非 <code>"confirmed planet"</code>，体现了科学报告的审慎原则：</p>
<pre><code># Test 15
required_keys = [
    "target_id",
    "estimated_transit_time_days",
    "estimated_depth_percent",
    "estimated_duration_hours",
    "number_of_low_flux_points",
    "confidence",
    "conclusion",
]

for key in required_keys:
    assert key in observation_card, f"Missing key: {key}"

assert observation_card["target_id"] == MYSTERY_TARGET["target_id"], "Use the correct target_id."
assert observation_card["confidence"] in ["low", "medium", "high"], "confidence should be low, medium, or high."
assert "confirmed" in observation_card["conclusion"].lower() or "possible" in observation_card["conclusion"].lower(), "Use careful scientific language."
print("Test 15 passed.")
</code></pre>
<p>输出：</p>
<pre><code>Test 15 passed.
</code></pre>
<hr />
<h1>9. 结果验证（Final reveal）</h1>
<blockquote>
<p>Final reveal</p>
<p>Run this only after your observation card is complete.</p>
<p>This tells you the hidden parameters used to generate the synthetic light curve.</p>
</blockquote>
<p>观测卡片填写完毕后，运行以下代码查看用于生成光变曲线的真实隐藏参数：</p>
<pre><code>truth = MYSTERY_TARGET["truth"]

print("Hidden target information")
print("-------------------------")
print("True period [days]:", truth["period_days"])
print("First transit time [days]:", truth["first_transit_time_days"])
print("True duration [hours]:", truth["duration_days"] * 24)
print("True depth [%]:", truth["depth_percent"])
print()
print("Compare your manual estimates with the hidden values.")
print("Tomorrow, we will learn how computers search for repeating transit patterns automatically.")
</code></pre>
<p>输出：</p>
<pre><code>Hidden target information
-------------------------
True period [days]: 5.606855586815562
First transit time [days]: 5.208084328426675
True duration [hours]: 5.871704212206591
True depth [%]: 1.548150177820702

Compare your manual estimates with the hidden values.
Tomorrow, we will learn how computers search for repeating transit patterns automatically.
</code></pre>
<p><strong>手动估算 vs 真实值对比：</strong></p>
<table>
<thead>
<tr>
<th>参数</th>
<th>手动估算</th>
<th>真实值</th>
</tr>
</thead>
<tbody>
<tr>
<td>凌星深度</td>
<td>∼2.47%</td>
<td>1.55%</td>
</tr>
<tr>
<td>凌星时长</td>
<td>∼5.0小时</td>
<td>5.87小时</td>
</tr>
<tr>
<td>周期</td>
<td>—</td>
<td>5.61天</td>
</tr>
</tbody>
</table>
<p>手动估算的凌星时长（5.0 小时）与真实值（5.87 小时）接近，但深度估算偏高（因为手动只取了最深点而真实深度是平均值）。周期无法通过手动方式可靠估计，这是下一步<strong>周期折叠分析</strong>（period-folding）的任务。</p>
<hr />
<h1>10. 提交清单（Submission checklist）</h1>
<blockquote>
<p>Submission checklist</p>
<p>Before you finish, make sure your notebook has:</p>
<ul>
<li>your name and team number;</li>
<li>completed code exercises;</li>
<li>all tests passed;</li>
<li>raw light curve plot;</li>
<li>cleaned and normalized light curve plot;</li>
<li>zoom plot near the deepest point;</li>
<li>highlighted low-flux points plot;</li>
<li>completed observation card;</li>
<li>careful conclusion: <strong>possible signal, not confirmed planet</strong>.</li>
</ul>
</blockquote>
<p>完成 notebook 前需确保：姓名和队伍编号已填写、代码练习已完成、所有测试通过、四张图均已绘制、观测卡片已填写、结论使用了审慎措辞。</p>
<hr />
<h1>11. 本节小结</h1>
<p>本文完成了以下内容：</p>
<ul>
<li>使用 NumPy 创建时间网格、叠加正态分布噪声，模拟真实望远镜观测数据。</li>
<li>通过 <code>np.isfinite()</code> 去除缺失值（NaN），使用 MAD 方法（<code>np.median</code> + 1.4826 缩放因子）检测并去除极端亮斑异常值。</li>
<li>用中位数归一化将流量统一到 1.0 附近。</li>
<li>利用 <code>np.argmin()</code> 定位最深点，通过布尔掩码和 <code>np.ptp()</code> 手动估算凌星时间和持续时间。</li>
<li>使用阈值法标记全局低流量点，通过可视化检查信号是否以固定间隔重复出现。</li>
<li>学习了科学报告的审慎原则：报告的是"可能信号"而非"确认发现"，使用 <code>confidence</code> 等级和 <code>possible</code> 措辞。</li>
<li>对比手动估算值与真实参数，理解自动化周期搜索（period-folding）的必要性。</li>
</ul>
<hr />
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="信息学"/>
  </entry>
  <entry>
    <title>国际夏令营｜我好像穿越了（bushi</title>
    <link href="https://luq-blog.xyz/posts/2026-07-07-sco-ysc-diary-03/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-07-07-sco-ysc-diary-03/</id>
    <published>2026-07-07T00:00:00.000Z</published>
    <updated>2026-07-20T00:00:00.000Z</updated>
    <summary>没什么，只是刚到这里而已。</summary>
    <content type="html"><![CDATA[<h1>1. 前言</h1>
<p>这篇文章其实是后面补的，因为以下是原本的内容，写的未免有些简略：</p>
<blockquote>
<p>到这里真的，太不习惯了。</p>
<p>我们在当地时间 7 点起床，然后 9 点的时候开始上课。</p>
<p>上课的时候，几乎什么都听不懂啊，怎么和英语听力相差这么多呢？</p>
<p>而且代码作业也有点多。</p>
<p>好累啊，今天什么都不想再写了，我去睡了。</p>
<p>（这天确实没多写，不过后续的文章会好一点。7月8日注。）</p>
</blockquote>
<h1>2. 参观校区</h1>
<p>早饭后（还是早饭前？），我们参观了校区。</p>
<blockquote>
<p>TODO: 补充照片</p>
</blockquote>
<h1>3. 第一节课，我学到了什么？</h1>
<p>一上来，老师就问我们能不能连上谷歌课堂。由于我之前在国内的时候注册过谷歌邮箱，所以我很容易就进入了谷歌课堂。不过另外两位中国的同学似乎有些吃力，貌似花了很长时间注册邮箱╯︿╰</p>
<p>老师先讲了一堆我们听不懂的东西。之后看来，可能就是使用凌日法来发现行星之类的事情。然后，就进入了敲代码环节。第一天我们没来，所以十分重要的开发环境的安装我们都没有了解。不过，老师一打开界面，我就放心了。这不是我之前就用过的 Jupyter Notebook 么？除此之外，老师还给我们介绍了 Colab，也是类似于 Jupyter 那样按照代码块运行的一个开发环境。</p>
<p>不过呢，老师主要在网页上直接运行，而我则习惯将相关的插件安装到 VS Code 中。这是因为，我的 VS Code 界面太好看了，总是有敲代码的冲动啊哈哈。</p>
<p>我们课上初步了解了如何使用光变曲线来侦探行星。相关代码及分析在这里：</p>
<p>&lt;a href="/posts/2026-07-07-astro-data-science-01-light-curve-detective/"&gt;笔记｜天文数据科学01｜光变曲线侦探&lt;/a&gt;</p>
<p>（不过其实课上几乎都是复制粘贴，对代码的理解完全是下课之后补充的U•ェ•*U）</p>
<h1>4. 来分个组吧</h1>
<p>我们方向有 3 个组，每组 5 个人。</p>
<p>（7 月 20 日注：但我之前一直以为我们组只有 4 个人的，哈哈）</p>
<p>来看看我们组的配置吧！</p>
<table>
<thead>
<tr>
<th>昵称</th>
<th>国籍</th>
<th>简介</th>
</tr>
</thead>
<tbody>
<tr>
<td>Luquiescene（我）</td>
<td>中国</td>
<td>一个普通的人类，负责数据分析部分</td>
</tr>
<tr>
<td>A 女士</td>
<td>吉尔吉斯斯坦</td>
<td>队长，喜欢独自行动，目标是考 MIT，负责很多内容</td>
</tr>
<tr>
<td>S 先生</td>
<td>乌兹别克斯坦</td>
<td>平时坐我旁边，和我成了好朋友，负责机器学习部分</td>
</tr>
<tr>
<td>S 女士</td>
<td>俄罗斯</td>
<td>看上去很成熟，负责做海报</td>
</tr>
<tr>
<td>不清楚</td>
<td>塔吉克斯坦</td>
<td>不会说英语，没有参与团队合作</td>
</tr>
</tbody>
</table>
<p>就是这样。以后这几天都要和他们相处了。</p>
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="思录"/>
  </entry>
  <entry>
    <title>国际夏令营｜出发</title>
    <link href="https://luq-blog.xyz/posts/2026-07-06-sco-ysc-dairy-02/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-07-06-sco-ysc-dairy-02/</id>
    <published>2026-07-06T00:00:00.000Z</published>
    <updated>2026-07-07T00:00:00.000Z</updated>
    <summary>记我第一次乘航班出国。</summary>
    <content type="html"><![CDATA[<h1>1. 发车！开往......</h1>
<p>（不过我乘坐的好像是飞机啊......）</p>
<p>2026年7月6日早上，我们在机场集合。照例经过安检，然后就可以登机了。我还是第一次乘坐国际航班，好激动啊♪(^∇^*)</p>
<p><img src="1.webp" alt="" /></p>
<p>飞机起飞于12时左右，我的位置比较靠后，一般来说，颠簸会比较大。不过幸运的是，全程居然完全没有遇到颠簸，这对不怎么坐飞机的我来说真是太好辣。</p>
<p><img src="2.webp" alt="" /></p>
<p>我们经过了某个湖（我也不知道叫什么名字，哈哈－_－b）</p>
<p><img src="3.webp" alt="" /></p>
<p>经过约2个小时的航行，我到达了位于哈萨克斯坦的一个机场，等待转机。</p>
<p>然后，一系列让我感到新奇的事情就发生了。</p>
<h1>2. 旅途中的停留</h1>
<h2>2.1 时差</h2>
<p>首先最能直观感受到的，就是时间了。哈萨克斯坦这边的时区是 UTC +5,比国内慢3小时。在机场等待的这段时间，总会产生“今天的时间好长啊”这样的感觉。</p>
<p>同时也不禁感慨，我们居然能在短短 2 小时内移动这么长的距离。下飞机后，看了眼时间，居然比起飞的时间还要早！我这是......穿越了！？（bushi</p>
<p><img src="4.webp" alt="" /></p>
<h2>2.2 语言和文字</h2>
<p>走进航站楼一看，更不得了。由于这个国家的主要语言不是英语，大部分都是我不认识的文字。这些字，我甚至以为是广告中为了视觉效果而加入的。不得不说，看不懂的文字确实有装饰作用（</p>
<p>在检查护照时，我感觉他们英语说得还蛮不错的，至少都能听懂，没出什么差错。</p>
<p>另外，国际机场里不是每位工作人员都会说英语哦，不要问我怎么知道的(￣▽￣)"</p>
<h2>2.3 一如既往的机场物价</h2>
<p>经常在机场购物的人都知道（这真的是正常人类么啊喂！），机场的东西比外面可能贵两倍不止。由于后续交通的原因，我在国外体验了一下在机场购物。</p>
<p>结果是，真的真的真的太贵啦(T_T)</p>
<p>不信你看，这是一瓶可乐的价格。</p>
<p><img src="5.webp" alt="" /></p>
<p>加上两个鸡腿，就要 50 多人民币了。</p>
<p><img src="6.webp" alt="" /></p>
<p>所以我没敢多买，饿不着就行了，哈哈。</p>
<h2>2.4 出发前没料到的事</h2>
<p>这里，居然看不了动漫！！！</p>
<p><img src="7.webp" alt="" /></p>
<p>我前一天晚上缓存了两部番，本以为做足了准备，结果是，即使缓存到本地，到海外也无法播放！如果你有出国计划，请以我为反面教材啊......</p>
<p>不过嘛，我有 B 计划。</p>
<p><img src="8.webp" alt="" /></p>
<p>所以最终没有影响哦。</p>
<h1>3. 果然，旅行只有出发前最开心</h1>
<h2>3.1 又要坐飞机吗</h2>
<p>对。不然总不能留在哈萨克斯坦的机场吧......</p>
<p>这次只有50分钟，感觉又坐了一次飞机，而已。只是刚起飞，就降落了一样。</p>
<p>我们就到达了吉尔吉斯共和国。</p>
<h2>3.2 新地图解锁！</h2>
<p>这里的时区是 UTC +6。你问我为什么记得这么清楚？当然是因为，这可是我人生中第一次进入另一个时区，而且一天改变了两次啊！</p>
<p>这里的时间比国内慢 2 个小时，对我来说刚刚好抵消了失眠的影响，简直就是完美适配啊。</p>
<h2>3.3 我不想散架啊</h2>
<p>等我们检查通过后，17 人分到了 2 辆 12 座的车上，这简直是 VIP 待遇啊！此外，居然还有本国的一辆警车护送我们，好大的牌面啊^o^y</p>
<p>不过，我还是高兴得太早了。因为，这里的道路实在是太破啦！！！除了刚出机场的道路，剩下的大部分是这样的：</p>
<p><img src="9.webp" alt="" /></p>
<p>难怪有警察护送啊......haha</p>
<p>关键是，我们坐了超过 4 小时车才到达目的地。</p>
<h1>4. 哈？我我我听不懂啊</h1>
<p>到这里之后，我们受到了志愿者的热烈欢迎。他们帮我们拿行李，带我们去大厅，指导我们去登记，还带我们去宿舍......只不过嘛，刚下车有点晕头转向、不明所以，一时间完全没有适应英语环境啊。这就导致他尴尬得拿出了 Chat GPT 进行翻译。</p>
<p>现在看来，这也正常，毕竟是在下第一次出国，还不太习惯(￣﹃￣)</p>
<h1>5. 这条件...真的是高中吗！？</h1>
<p>我们同宿舍的都是中国人，其中一位和我选了同样的课题，所以生活习惯什么的几乎没有不同。</p>
<p>我们住的可是有独立卫浴的三人间哦！这里时间紧张，我拍了两张照片给你们看看吧：</p>
<p><img src="10.webp" alt="" /></p>
<p><img src="11.webp" alt="" /></p>
<p>不总结了。累了，我先睡下吧。</p>
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="思录"/>
  </entry>
  <entry>
    <title>数据分析｜DataFrame的介绍和创建</title>
    <link href="https://luq-blog.xyz/posts/2026-07-04-data-notes-17-df-intro/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-07-04-data-notes-17-df-intro/</id>
    <published>2026-07-04T00:00:00.000Z</published>
    <updated>2026-07-04T00:00:00.000Z</updated>
    <summary>本节介绍 Pandas DataFrame 的二维表格结构，包括行索引、列标签的概念以及通过 Series 和字典创建 DataFrame 的方法。</summary>
    <content type="html"><![CDATA[<h1>17.1 DataFrame 结构图解</h1>
<p>DataFrame 是 Pandas 中的二维表格结构，由两大部分组成：</p>
<ol>
<li><strong>Index（行索引）</strong>：每一行称为一条 Record（记录）；</li>
<li><strong>多列 Series</strong>：每一列都是一个独立的 Series 对象，各列可拥有不同数据类型。</li>
</ol>
<p>整体类似 Excel 整张工作表，通过<strong>行索引 + 列标签</strong>共同定位每一个单元格。</p>
<pre><code>    name  score  age
1    tom   60.5   15
2    tom   60.5   15
3   jack   80.0   15
4  alice   30.6   20
5    bob   70.0   26
6  allen   83.5   30
</code></pre>
<ul>
<li>行索引：<code>1, 2, 3, 4, 5, 6</code></li>
<li>列标签：<code>name, score, age</code></li>
<li>每一列（如 <code>score</code>）本质上是一个 Series</li>
</ul>
<hr />
<h1>17.2 DataFrame 的创建方式</h1>
<h2>17.2.1 通过 Series 创建</h2>
<pre><code>import pandas as pd
import numpy as np

s1 = pd.Series([1, 2, 3, 4, 5])
s2 = pd.Series([6, 7, 8, 9, 10])
df = pd.DataFrame({'第一列': s1, '第二列': s2})
print(type(df['第一列']))  # 每一列是一个 Series
</code></pre>
<p>输出：</p>
<pre><code>&lt;class 'pandas.core.series.Series'&gt;
</code></pre>
<p>将多个 Series 传入字典，键作为列名，值作为列数据。</p>
<h2>17.2.2 通过字典创建</h2>
<pre><code>df = pd.DataFrame(
    {
        'name': ['tom', 'tom', 'jack', 'alice', 'bob', 'allen'],
        'age': [15, 15, 15, 20, 26, 30],
        'score': [60.5, 60.5, 80, 30.6, 70, 83.5]
    },
    index=[1, 2, 3, 4, 5, 6],
    columns=['name', 'score', 'age']
)
print(df)
</code></pre>
<p>输出：</p>
<pre><code>    name  score  age
1    tom   60.5   15
2    tom   60.5   15
3   jack   80.0   15
4  alice   30.6   20
5    bob   70.0   26
6  allen   83.5   30
</code></pre>
<p><strong>要点说明：</strong></p>
<ul>
<li>字典的键 → 列标签（<code>columns</code>）</li>
<li><code>index</code> 参数：指定自定义行索引</li>
<li><code>columns</code> 参数：指定列的显示顺序（可调整列的顺序）</li>
<li>各列可以是不同数据类型（<code>name</code> 为字符串、<code>score</code> 为浮点、<code>age</code> 为整数）</li>
</ul>
<hr />
<h1>17.3 本节小结</h1>
<p>本节完成了以下内容：</p>
<ul>
<li>理解了 DataFrame 的二维结构：行索引 + 多列 Series。</li>
<li>掌握了两种创建方式：通过 Series 字典创建、通过普通字典配合 <code>index</code> 和 <code>columns</code> 参数创建。</li>
<li>了解了 DataFrame 中每一列本质上是一个独立的 Series 对象。</li>
</ul>
<p>下一节我们将学习 <strong>DataFrame 的属性</strong>。</p>
<hr />
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="信息学"/>
  </entry>
  <entry>
    <title>数据分析｜DataFrame的属性</title>
    <link href="https://luq-blog.xyz/posts/2026-07-04-data-notes-18-df-attributes/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-07-04-data-notes-18-df-attributes/</id>
    <published>2026-07-04T00:00:00.000Z</published>
    <updated>2026-07-05T00:00:00.000Z</updated>
    <summary>本节介绍 Pandas DataFrame 的常用属性，包括 index、columns、values、shape、dtypes、T 等，掌握 DataFrame 元信息的获取方式。</summary>
    <content type="html"><![CDATA[<h1>18.1 DataFrame 属性总览</h1>
<table>
<thead>
<tr>
<th>属性</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>index</code></td>
<td>DataFrame 的行索引</td>
</tr>
<tr>
<td><code>columns</code></td>
<td>DataFrame 的列标签</td>
</tr>
<tr>
<td><code>values</code></td>
<td>DataFrame 的值（numpy 数组）</td>
</tr>
<tr>
<td><code>dtypes</code></td>
<td>每一列的数据类型</td>
</tr>
<tr>
<td><code>shape</code></td>
<td>DataFrame 的形状（行数, 列数）</td>
</tr>
<tr>
<td><code>ndim</code></td>
<td>DataFrame 的维度（固定为 2）</td>
</tr>
<tr>
<td><code>size</code></td>
<td>DataFrame 的元素总个数</td>
</tr>
<tr>
<td><code>loc[]</code></td>
<td>显式索引，按行列标签索引或切片</td>
</tr>
<tr>
<td><code>iloc[]</code></td>
<td>隐式索引，按行列位置索引或切片</td>
</tr>
<tr>
<td><code>at[]</code></td>
<td>使用行列标签访问单个元素</td>
</tr>
<tr>
<td><code>iat[]</code></td>
<td>使用行列位置访问单个元素</td>
</tr>
<tr>
<td><code>T</code></td>
<td>行列转置</td>
</tr>
</tbody>
</table>
<hr />
<h1>18.2 属性访问示例</h1>
<p>沿用第 17 节创建的 DataFrame：</p>
<pre><code>import pandas as pd
import numpy as np

df = pd.DataFrame(
    {
        'name': ['tom', 'tom', 'jack', 'alice', 'bob', 'allen'],
        'score': [60.5, 60.5, 80, 30.6, 70, 83.5],
        'age': [15, 15, 15, 20, 26, 30]
    },
    index=[1, 2, 3, 4, 5, 6]
)
</code></pre>
<p><strong>基础属性：</strong></p>
<pre><code>print('行索引：', df.index)
print('列标签：', df.columns)
print('值：\n', df.values)
print('维度：', df.ndim)
print('形状：', df.shape)
print('元素个数：', df.size)
print('数据类型：\n', df.dtypes)
</code></pre>
<p>输出：</p>
<pre><code>行索引： Index([1, 2, 3, 4, 5, 6], dtype='int64')
列标签： Index(['name', 'score', 'age'], dtype='object')
值：
 [['tom' 60.5 15]
 ['tom' 60.5 15]
 ['jack' 80.0 15]
 ['alice' 30.6 20]
 ['bob' 70.0 26]
 ['allen' 83.5 30]]
维度： 2
形状： (6, 3)
元素个数： 18
数据类型：
 name      object
score    float64
age        int64
dtype: object
</code></pre>
<p><strong>行列转置：</strong></p>
<pre><code>print(df.T)
print(df.T.index)
</code></pre>
<p>输出：</p>
<pre><code>          1     2     3      4     5      6
name    tom   tom  jack  alice   bob  allen
score  60.5  60.5  80.0   30.6  70.0   83.5
age      15    15    15     20    26     30
Index(['name', 'score', 'age'], dtype='object')
</code></pre>
<p>转置后原来的行索引变成列标签，列标签变成行索引。</p>
<hr />
<h1>18.3 本节小结</h1>
<p>本节完成了以下内容：</p>
<ul>
<li>掌握了 DataFrame 的常用属性：<code>index</code>、<code>columns</code>、<code>values</code>、<code>dtypes</code>、<code>shape</code>、<code>ndim</code>、<code>size</code>。</li>
<li>理解了 <code>dtypes</code>（返回每列数据类型）与 <code>dtype</code>（Series 属性）的区别。</li>
<li>学会了通过 <code>T</code> 属性做行列转置。</li>
</ul>
<p>下一节我们将学习 <strong>DataFrame 的访问</strong>。</p>
<hr />
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="信息学"/>
  </entry>
  <entry>
    <title>数据分析｜DataFrame的访问</title>
    <link href="https://luq-blog.xyz/posts/2026-07-04-data-notes-19-df-access/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-07-04-data-notes-19-df-access/</id>
    <published>2026-07-04T00:00:00.000Z</published>
    <updated>2026-07-04T00:00:00.000Z</updated>
    <summary>本节介绍 Pandas DataFrame 的数据访问方式，包括 loc、iloc、at、iat 索引器以及布尔索引、单列/多列获取、抽样等操作。</summary>
    <content type="html"><![CDATA[<h1>19.1 按行列访问（loc、iloc、at、iat）</h1>
<p>沿用前两节的 DataFrame：</p>
<pre><code>import pandas as pd
import numpy as np

df = pd.DataFrame(
    {
        'name': ['tom', 'tom', 'jack', 'alice', 'bob', 'allen'],
        'score': [60.5, 60.5, 80, 30.6, 70, 83.5],
        'age': [15, 15, 15, 20, 26, 30]
    },
    index=[1, 2, 3, 4, 5, 6]
)
</code></pre>
<p><strong>访问某行：</strong></p>
<pre><code>print(df.loc[4])    # 按标签取第4行
print(df.iloc[3])   # 按位置取第4行（索引从0开始）
</code></pre>
<p>输出：</p>
<pre><code>name     alice
score     30.6
age         20
Name: 4, dtype: object
name     alice
score     30.6
age         20
Name: 4, dtype: object
</code></pre>
<p><strong>访问某列：</strong></p>
<pre><code>print(df.loc[:, 'name'])   # 所有行的 name 列
print(df.iloc[:, 0])        # 所有行的第1列
</code></pre>
<p>输出：</p>
<pre><code>1      tom
2      tom
3     jack
4    alice
5      bob
6    allen
Name: name, dtype: object
1      tom
2      tom
3     jack
4    alice
5      bob
6    allen
Name: name, dtype: object
</code></pre>
<p><strong>访问单个元素：</strong></p>
<pre><code>print(df.at[3, 'score'])    # 标签访问
print(df.iat[2, 1])          # 位置访问（第3行第2列）
print(df.loc[3, 'score'])    # loc 同样可访问单个元素
print(df.iloc[2, 1])         # iloc 同样可访问单个元素
</code></pre>
<p>输出：</p>
<pre><code>80.0
80.0
80.0
80.0
</code></pre>
<hr />
<h1>19.2 获取单列与多列</h1>
<p><strong>获取单列（返回 Series）：</strong></p>
<pre><code>print(df['name'])
print(type(df['name']))   # &lt;class 'pandas.core.series.Series'&gt;
print(df.name)            # 也可通过属性方式访问
</code></pre>
<p>输出：</p>
<pre><code>1      tom
2      tom
3     jack
4    alice
5      bob
6    allen
Name: name, dtype: object
&lt;class 'pandas.core.series.Series'&gt;
1      tom
2      tom
3     jack
4    alice
5      bob
6    allen
Name: name, dtype: object
</code></pre>
<p><strong>获取多列（返回 DataFrame）：</strong></p>
<pre><code>print(df[['name', 'score']])
print(type(df[['name', 'score']]))  # &lt;class 'pandas.core.frame.DataFrame'&gt;
</code></pre>
<p>输出：</p>
<pre><code>    name  score
1    tom   60.5
2    tom   60.5
3   jack   80.0
4  alice   30.6
5    bob   70.0
6  allen   83.5
&lt;class 'pandas.core.frame.DataFrame'&gt;
</code></pre>
<p><strong>要点：</strong> <code>df['col']</code> 返回 Series，<code>df[['col1', 'col2']]</code>（双层方括号）返回 DataFrame。</p>
<hr />
<h1>19.3 数据预览与筛选</h1>
<p><strong>查看部分数据：</strong></p>
<pre><code>print(df.head(2))   # 前2行
print(df.tail(3))   # 后3行
</code></pre>
<p>输出：</p>
<pre><code>  name  score  age
1  tom   60.5   15
2  tom   60.5   15
    name  score  age
4  alice   30.6   20
5    bob   70.0   26
6  allen   83.5   30
</code></pre>
<p><strong>布尔索引筛选：</strong></p>
<pre><code>print(df[df.score &gt; 70])                        # 单条件
print(df[(df.score &gt; 70) &amp; (df.age &lt; 20)])       # 多条件（与）
</code></pre>
<p>输出：</p>
<pre><code>    name  score  age
3   jack   80.0   15
6  allen   83.5   30
   name  score  age
3  jack   80.0   15
</code></pre>
<p><strong>随机抽样：</strong></p>
<pre><code>print(df.sample(3))  # 随机抽取3行
</code></pre>
<p>输出（结果随机）：</p>
<pre><code>    name  score  age
6  allen   83.5   30
1    tom   60.5   15
4  alice   30.6   20
</code></pre>
<hr />
<h1>19.4 本节小结</h1>
<p>本节完成了以下内容：</p>
<ul>
<li>掌握了 <code>loc</code>（按标签）、<code>iloc</code>（按位置）、<code>at</code>、<code>iat</code> 四种索引器的用法。</li>
<li>理解了单列（<code>df['col']</code> 返回 Series）与多列（<code>df[['c1', 'c2']]</code> 返回 DataFrame）的区别。</li>
<li>学会了 <code>head()</code>、<code>tail()</code> 预览、布尔索引多条件筛选（<code>&amp;</code> / <code>|</code>）、<code>sample()</code> 随机抽样。</li>
</ul>
<p>下一节我们将学习 <strong>DataFrame 的常用方法</strong>。</p>
<hr />
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="信息学"/>
  </entry>
  <entry>
    <title>数据分析｜DataFrame的常用方法</title>
    <link href="https://luq-blog.xyz/posts/2026-07-04-data-notes-20-df-methods/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-07-04-data-notes-20-df-methods/</id>
    <published>2026-07-04T00:00:00.000Z</published>
    <updated>2026-07-05T00:00:00.000Z</updated>
    <summary>本节介绍 Pandas DataFrame 的常用方法，包括统计描述、缺失值判断、去重排序、值替换和频率统计等操作。</summary>
    <content type="html"><![CDATA[<h1>20.1 DataFrame 常用方法总览</h1>
<h2>常用方法总览</h2>
<table>
<thead>
<tr>
<th>方法</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>head()</code></td>
<td>查看前 n 行数据，默认 5 行</td>
</tr>
<tr>
<td><code>tail()</code></td>
<td>查看后 n 行数据，默认 5 行</td>
</tr>
<tr>
<td><code>isin()</code></td>
<td>判断元素是否包含在参数集合中</td>
</tr>
<tr>
<td><code>isna()</code></td>
<td>判断是否为缺失值（如 <code>NaN</code> 或 <code>None</code>）</td>
</tr>
<tr>
<td><code>sum()</code></td>
<td>求和，自动忽略缺失值</td>
</tr>
<tr>
<td><code>mean()</code></td>
<td>平均值</td>
</tr>
<tr>
<td><code>min()</code></td>
<td>最小值</td>
</tr>
<tr>
<td><code>max()</code></td>
<td>最大值</td>
</tr>
<tr>
<td><code>var()</code></td>
<td>方差</td>
</tr>
<tr>
<td><code>std()</code></td>
<td>标准差</td>
</tr>
<tr>
<td><code>median()</code></td>
<td>中位数</td>
</tr>
<tr>
<td><code>mode()</code></td>
<td>众数（可返回多个）</td>
</tr>
<tr>
<td><code>quantile(q)</code></td>
<td>分位数，q 取 0~1 之间</td>
</tr>
<tr>
<td><code>describe()</code></td>
<td>常见统计信息（count、mean、std、min、25%、50%、75%、max）</td>
</tr>
<tr>
<td><code>value_counts()</code></td>
<td>每个唯一值的出现次数</td>
</tr>
<tr>
<td><code>count()</code></td>
<td>非缺失值数量</td>
</tr>
<tr>
<td><code>duplicated()</code></td>
<td>判断行是否重复</td>
</tr>
<tr>
<td><code>drop_duplicates()</code></td>
<td>去除重复行</td>
</tr>
<tr>
<td><code>sample()</code></td>
<td>随机抽样</td>
</tr>
<tr>
<td><code>sort_index()</code></td>
<td>按索引排序</td>
</tr>
<tr>
<td><code>sort_values()</code></td>
<td>按值排序</td>
</tr>
<tr>
<td><code>replace()</code></td>
<td>替换值</td>
</tr>
<tr>
<td><code>nlargest()</code></td>
<td>返回某列最大的 n 条数据</td>
</tr>
<tr>
<td><code>nsmallest()</code></td>
<td>返回某列最小的 n 条数据</td>
</tr>
</tbody>
</table>
<hr />
<h1>20.2 统计描述方法</h1>
<pre><code>import pandas as pd
import numpy as np

df = pd.DataFrame(
    {
        'name': ['tom', 'tom', 'jack', 'alice', 'bob', 'allen'],
        'score': [60.5, 60.5, 80, 30.6, 70, 83.5],
        'age': [15, 15, 15, 20, 26, 30]
    },
    index=[1, 2, 3, 4, 5, 6]
)
</code></pre>
<p><strong>描述性统计：</strong></p>
<pre><code>print(df.describe())
</code></pre>
<p>输出：</p>
<pre><code>           score        age
count   6.000000   6.000000
mean   64.183333  20.166667
std    19.037376   6.493587
min    30.600000  15.000000
25%    60.500000  15.000000
50%    65.250000  17.500000
75%    77.500000  24.500000
max    83.500000  30.000000
</code></pre>
<p><code>describe()</code> 自动跳过非数值列（如 <code>name</code>），仅对数值列做统计。</p>
<p><strong>常用统计计算：</strong></p>
<pre><code>print(df['score'].sum())         # 求和
print(df.score.max())            # 最大值
print(df.age.min())              # 最小值
print(df.score.mean())           # 平均值
print(df.score.median())         # 中位数
print(df.age.mode())             # 众数
print(df.score.std())            # 标准差
print(df.score.var())            # 方差
print(df.score.quantile(0.25))   # 第25百分位数
print(df.count())                # 每列非缺失值数量
</code></pre>
<p>输出：</p>
<pre><code>385.1
83.5
15
64.18333333333334
65.25
0    15
Name: age, dtype: int64
19.037375519400424
362.4216666666666
60.5
name     6
score    6
age      6
dtype: int64
</code></pre>
<hr />
<h1>20.3 去重与排序</h1>
<p><strong>去重：</strong></p>
<pre><code>print(df.drop_duplicates())                  # 去除完全重复的行
print(df.duplicated(subset=['age']))  # 判断指定列的重复情况
</code></pre>
<p>输出：</p>
<pre><code>    name  score  age
1    tom   60.5   15
3   jack   80.0   15
4  alice   30.6   20
5    bob   70.0   26
6  allen   83.5   30
1    False
2     True
3     True
4    False
5    False
6    False
dtype: bool
</code></pre>
<p><code>duplicated(subset=['age'])</code> 检查 <code>age</code> 列的值是否重复，返回布尔 Series。</p>
<p><strong>排序：</strong></p>
<pre><code>print(df.sort_index(ascending=False))
print(df.sort_values(by=['score', 'age'], ascending=[False, True]))
</code></pre>
<p>输出：</p>
<pre><code>    name  score  age
6  allen   83.5   30
5    bob   70.0   26
4  alice   30.6   20
3   jack   80.0   15
2    tom   60.5   15
1    tom   60.5   15
    name  score  age
3   jack   80.0   15
6  allen   80.0   30
5    bob   70.0   26
1    tom   60.5   15
2    tom   60.5   15
4  alice   30.6   20
</code></pre>
<p><code>sort_values()</code> 支持多列排序：先按 <code>score</code> 降序，再按 <code>age</code> 升序。</p>
<p><strong>取最大/最小 n 行：</strong></p>
<pre><code>print(df.nlargest(2, columns=['score', 'age']))
print(df.nsmallest(2, columns=['score', 'age']))
</code></pre>
<p>输出：</p>
<pre><code>    name  score  age
6  allen   83.5   30
3   jack   80.0   15
    name  score  age
4  alice   30.6   20
1    tom   60.5   15
</code></pre>
<hr />
<h1>20.4 其他实用方法</h1>
<pre><code>print(df.isin(['jack', 20]))     # 判断元素是否在集合中
print(df.isna())                  # 判断是否为缺失值
print(df.value_counts())          # 统计每行出现的频次
print(df.replace(15, 30))        # 将 15 替换为 30
print(df.sample(2))              # 随机抽样 2 行
</code></pre>
<p>输出（结果随机）：</p>
<pre><code>    name  score    age
1  False  False  False
2  False  False  False
3   True  False  False
4  False  False   True
5  False  False  False
6  False  False  False
    name  score    age
1  False  False  False
...
    name  score  age
1    tom   60.5   30
2    tom   60.5   30
3   jack   80.0   30
4  alice   30.6   20
5    bob   70.0   26
6  allen   83.5   30
</code></pre>
<hr />
<h1>20.5 本节小结</h1>
<p>本节完成了以下内容：</p>
<ul>
<li>掌握了 <code>describe()</code>、<code>sum()</code>、<code>mean()</code>、<code>max()</code>、<code>min()</code>、<code>median()</code>、<code>mode()</code>、<code>std()</code>、<code>var()</code>、<code>quantile()</code> 等统计方法。</li>
<li>掌握了 <code>drop_duplicates()</code>、<code>duplicated()</code> 去重方法。</li>
<li>掌握了 <code>sort_index()</code>、<code>sort_values()</code>、<code>nlargest()</code>、<code>nsmallest()</code> 排序与取值方法。</li>
<li>掌握了 <code>isin()</code>、<code>isna()</code>、<code>value_counts()</code>、<code>replace()</code>、<code>sample()</code> 等实用方法。</li>
</ul>
<p>下一节我们将通过 <strong>DataFrame 案例</strong> 综合练习这些方法。</p>
<hr />
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="信息学"/>
  </entry>
  <entry>
    <title>数据分析｜DataFrame案例</title>
    <link href="https://luq-blog.xyz/posts/2026-07-04-data-notes-21-df-cases/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-07-04-data-notes-21-df-cases/</id>
    <published>2026-07-04T00:00:00.000Z</published>
    <updated>2026-07-04T00:00:00.000Z</updated>
    <summary>本节通过学生成绩、销售数据、电商用户行为三个实际案例，综合运用 DataFrame 的创建、列运算、条件筛选、排序和聚合等操作。</summary>
    <content type="html"><![CDATA[<h1>21.1 案例概述</h1>
<p>本节通过三个实际案例，综合运用 DataFrame 的创建、列运算、条件筛选、排序和聚合操作。</p>
<pre><code>import numpy as np
import pandas as pd
</code></pre>
<hr />
<h1>21.2 学生成绩分析</h1>
<pre><code>data = {
    '姓名': ['张三', '李四', '王五', '赵六', '钱七'],
    '数学': [85, 92, 78, 88, 95],
    '英语': [90, 88, 85, 92, 80],
    '物理': [75, 80, 88, 85, 90],
}
scores = pd.DataFrame(data)
print(scores)
</code></pre>
<p>输出：</p>
<pre><code>   姓名  数学  英语  物理
0  张三  85  90  75
1  李四  92  88  80
2  王五  78  85  88
3  赵六  88  92  85
4  钱七  95  80  90
</code></pre>
<p><strong>计算总分和平均分：</strong></p>
<pre><code>scores['总分'] = scores[['数学', '英语', '物理']].sum(axis=1)
scores['平均分'] = scores['总分'] / 3
print(scores)
</code></pre>
<p>输出：</p>
<pre><code>   姓名  数学  英语  物理   总分        平均分
0  张三  85  90  75  250  83.333333
1  李四  92  88  80  260  86.666667
2  王五  78  85  88  251  83.666667
3  赵六  88  92  85  265  88.333333
4  钱七  95  80  90  265  88.333333
</code></pre>
<p><code>sum(axis=1)</code> 按行求和（沿列方向），计算每位学生的三科总分。</p>
<p><strong>筛选条件组合：</strong></p>
<pre><code># 数学高于90分 或 英语高于85分
print(scores[(scores['数学'] &gt; 90) | (scores['英语'] &gt; 85)])
</code></pre>
<p>输出：</p>
<pre><code>   姓名  数学  英语  物理   总分        平均分
0  张三  85  90  75  250  83.333333
1  李四  92  88  80  260  86.666667
3  赵六  88  92  85  265  88.333333
4  钱七  95  80  90  265  88.333333
</code></pre>
<p><code>|</code> 表示逻辑或，<code>&amp;</code> 表示逻辑与。注意每个条件需要用括号括起来。</p>
<p><strong>按总分排序取前 3 名：</strong></p>
<pre><code>print(scores.nlargest(3, columns='总分'))
</code></pre>
<p>输出：</p>
<pre><code>   姓名  数学  英语  物理   总分        平均分
3  赵六  88  92  85  265  88.333333
4  钱七  95  80  90  265  88.333333
1  李四  92  88  80  260  86.666667
</code></pre>
<p><strong>涉及方法：</strong> <code>sum(axis=1)</code>、<code>|</code> 条件组合、<code>nlargest()</code>、新增列运算。</p>
<hr />
<h1>21.3 销售数据分析</h1>
<pre><code>data = {
    '产品名称': ['A', 'B', 'C', 'D'],
    '单价': [100, 150, 200, 120],
    '销量': [50, 30, 20, 40]
}
df = pd.DataFrame(data)
print(df)
</code></pre>
<p>输出：</p>
<pre><code>  产品名称   单价  销量
0    A  100  50
1    B  150  30
2    C  200  20
3    D  120  40
</code></pre>
<p><strong>计算总销售额并排序：</strong></p>
<pre><code>df['总销售额'] = df['单价'] * df['销量']
print(df.nlargest(1, columns='总销售额'))  # 销售额最高的产品
print(df.sort_values('总销售额', ascending=False))
</code></pre>
<p>输出：</p>
<pre><code>  产品名称   单价  销量  总销售额
0    A  100  50  5000
  产品名称   单价  销量  总销售额
0    A  100  50  5000
3    D  120  40  4800
1    B  150  30  4500
2    C  200  20  4000
</code></pre>
<p><strong>涉及方法：</strong> 列间运算、<code>nlargest()</code>、<code>sort_values()</code>。</p>
<hr />
<h1>21.4 电商用户行为分析</h1>
<pre><code>data = {
    '用户ID': [101, 102, 103, 104, 105],
    '用户名': ['Alice', 'Bob', 'Charlie', 'David', 'Eve'],
    '商品类别': ['电子产品', '服饰', '电子产品', '家居', '服饰'],
    '商品单价': [1200, 300, 800, 150, 200],
    '购买数量': [1, 3, 2, 5, 4],
}
df = pd.DataFrame(data)
print(df)
</code></pre>
<p>输出：</p>
<pre><code>   用户ID      用户名  商品类别  商品单价  购买数量
0   101    Alice  电子产品  1200     1
1   102      Bob    服饰   300     3
2   103  Charlie  电子产品   800     2
3   104    David    家居   150     5
4   105      Eve    服饰   200     4
</code></pre>
<p><strong>计算消费金额与最优用户：</strong></p>
<pre><code>df['消费金额'] = df['商品单价'] * df['购买数量']
print(df.nlargest(1, columns='消费金额'))
</code></pre>
<p>输出：</p>
<pre><code>   用户ID      用户名  商品类别  商品单价  购买数量  消费金额
2   103  Charlie  电子产品   800     2  1600
</code></pre>
<pre><code>print('平均消费金额:', df['消费金额'].mean())
</code></pre>
<p>输出：</p>
<pre><code>平均消费金额: 1050.0
</code></pre>
<p><strong>分类筛选统计：</strong></p>
<pre><code># 统计电子产品的总购买数量
print(df[df['商品类别'] == '电子产品']['购买数量'].sum())
</code></pre>
<p>输出：</p>
<pre><code>3
</code></pre>
<p><strong>分析流程：</strong> 先通过布尔索引 <code>df['商品类别'] == '电子产品'</code> 筛选出电子产品行，再取 <code>['购买数量']</code> 列，最后 <code>sum()</code> 求和。</p>
<p><strong>涉及方法：</strong> 列运算、<code>nlargest()</code>、<code>mean()</code>、布尔索引 + 列筛选 + <code>sum()</code> 链式操作。</p>
<hr />
<h1>21.5 本节小结</h1>
<p>本节通过三个案例综合运用了 DataFrame 的核心操作：</p>
<table>
<thead>
<tr>
<th>案例</th>
<th>涉及方法</th>
</tr>
</thead>
<tbody>
<tr>
<td>学生成绩分析</td>
<td><code>sum(axis=1)</code>、<code>nlargest()</code>、<code>|</code> 条件筛选、新增列</td>
</tr>
<tr>
<td>销售数据分析</td>
<td>列间运算、<code>nlargest()</code>、<code>sort_values()</code></td>
</tr>
<tr>
<td>电商用户分析</td>
<td>布尔索引筛选、链式操作、<code>mean()</code>、<code>sum()</code></td>
</tr>
</tbody>
</table>
<p>下一节我们将学习 <strong>Pandas 数据读取与写入</strong>。</p>
<hr />
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="信息学"/>
  </entry>
  <entry>
    <title>数据分析｜Pandas介绍</title>
    <link href="https://luq-blog.xyz/posts/2026-07-03-data-notes-13-pandas-intro/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-07-03-data-notes-13-pandas-intro/</id>
    <published>2026-07-03T00:00:00.000Z</published>
    <updated>2026-07-03T00:00:00.000Z</updated>
    <summary>本节介绍 Pandas 库的基础概念，包括其核心设计理念、与 NumPy 的关系，以及 Series 和 DataFrame 两大核心数据结构的对比。</summary>
    <content type="html"><![CDATA[<h1>13.1 Pandas简介</h1>
<h2>Pandas 是什么？</h2>
<p>Pandas 是 Python 数据分析工具链中最核心的库，充当数据读取、清洗、分析、统计、输出的高效工具。</p>
<p>Pandas 提供了易于使用的数据结构和数据分析工具，特别适用于处理结构化数据，如表格型数据（类似于 Excel 表格）。</p>
<p>Pandas 是数据科学和分析领域中常用的工具之一，它使得用户能够轻松地从各种数据源中导入数据，并对数据进行高效的操作和分析。</p>
<h2>Pandas 底层与核心设计理念</h2>
<p>Pandas 是基于 NumPy 构建的专门为处理表格和混杂数据设计的 Python 库，核心设计理念包括：</p>
<ul>
<li><strong>标签化数据结构</strong>：提供带标签的轴</li>
<li><strong>灵活处理缺失数据</strong>：内置 <code>NaN</code> 处理机制</li>
<li><strong>智能数据对齐</strong>：自动按标签对齐数据</li>
<li><strong>强大 IO 工具</strong>：支持从 CSV、Excel、SQL 等 20+ 数据源读写</li>
<li><strong>时间序列处理</strong>：原生支持日期时间处理和频率转换</li>
</ul>
<hr />
<h1>13.2 Pandas 两大核心数据结构对比</h1>
<p>Pandas 的核心是两种数据结构：<strong>Series</strong>（一维）和 <strong>DataFrame</strong>（二维）。</p>
<table>
<thead>
<tr>
<th>特性</th>
<th>Series</th>
<th>DataFrame</th>
</tr>
</thead>
<tbody>
<tr>
<td>维度</td>
<td>一维</td>
<td>二维</td>
</tr>
<tr>
<td>索引</td>
<td>单索引</td>
<td>行索引 + 列名</td>
</tr>
<tr>
<td>数据存储</td>
<td>同质化数据类型</td>
<td>各列可不同数据类型</td>
</tr>
<tr>
<td>类比</td>
<td>Excel 单列</td>
<td>整张 Excel 工作表</td>
</tr>
<tr>
<td>创建方式</td>
<td><code>pd.Series([1, 2, 3])</code></td>
<td><code>pd.DataFrame({'col': [1, 2, 3]})</code></td>
</tr>
</tbody>
</table>
<ul>
<li><strong>Series</strong>：类似于一维数组，可以理解为 Excel 表格中的单列数据，每个元素都有一个对应的索引标签。</li>
<li><strong>DataFrame</strong>：类似于二维表格，可以理解为整张 Excel 工作表，由行索引和列名共同定位数据。</li>
</ul>
<hr />
<h1>13.3 本节小结</h1>
<p>本节完成了以下内容：</p>
<ul>
<li>了解了 Pandas 的定位：基于 NumPy 构建的表格数据处理核心库。</li>
<li>掌握了 Pandas 的五大核心设计理念：标签化、缺失值处理、数据对齐、IO 工具、时间序列。</li>
<li>理解了 Series（一维）与 DataFrame（二维）的区别与联系。</li>
</ul>
<p>下一节我们将学习 <strong>Series 的创建、属性与索引</strong>。</p>
<hr />
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="信息学"/>
  </entry>
  <entry>
    <title>数据分析｜Series的创建、属性与索引</title>
    <link href="https://luq-blog.xyz/posts/2026-07-03-data-notes-14-series-basics/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-07-03-data-notes-14-series-basics/</id>
    <published>2026-07-03T00:00:00.000Z</published>
    <updated>2026-07-05T00:00:00.000Z</updated>
    <summary>本节介绍 Pandas Series 的结构、创建方法、常用属性以及索引访问方式，掌握 Series 的核心使用方法。</summary>
    <content type="html"><![CDATA[<h1>14.1 Series 结构</h1>
<p>Series 是一种<strong>一维数组型对象</strong>，包含两个核心组成部分：</p>
<ul>
<li><strong>值序列（values）</strong>：存储的实际数据</li>
<li><strong>索引（index）</strong>：每个数据对应的标签</li>
</ul>
<h2>结构图解</h2>
<pre><code>A
1    1
2    2
3    3
4    4
dtype: int64
</code></pre>
<p>图示中 Series 分为三部分：</p>
<ol>
<li><strong>Series Index</strong>：左侧数字 1、2、3、4，代表序列索引</li>
<li><strong>Series Name</strong>：顶部标识 A，代表序列名称</li>
<li><strong>Series Values</strong>：右侧数字 1、2、3、4，代表序列存储的数据</li>
</ol>
<hr />
<h1>14.2 Series 的创建</h1>
<h2>14.2.1 通过列表创建</h2>
<pre><code>import pandas as pd

s = pd.Series([1, 2, 3, 4, 5])
print(s)
</code></pre>
<p>输出：</p>
<pre><code>0    1
1    2
2    3
3    4
4    5
dtype: int64
</code></pre>
<p>默认索引为从 0 开始的整数序列。</p>
<h2>14.2.2 自定义索引</h2>
<p>通过 <code>index</code> 参数可以为 Series 指定自定义的索引标签：</p>
<pre><code>s = pd.Series([1, 2, 3, 4, 5], index=['A', 'B', 'C', 'D', 'E'])
print(s)
</code></pre>
<p>输出：</p>
<pre><code>A    1
B    2
C    3
D    4
E    5
dtype: int64
</code></pre>
<h2>14.2.3 指定名称</h2>
<p>通过 <code>name</code> 参数可以为 Series 命名：</p>
<pre><code>s = pd.Series([1, 2, 3, 4, 5], index=['A', 'B', 'C', 'D', 'E'], name='月份')
print(s)
</code></pre>
<p>输出：</p>
<pre><code>A    1
B    2
C    3
D    4
E    5
Name: 月份, dtype: int64
</code></pre>
<h2>14.2.4 通过字典创建</h2>
<p>将字典的键作为索引、值作为数据创建 Series：</p>
<pre><code>s = pd.Series({'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5})
print(s)

# 通过已有 Series 和指定索引提取子集
s1 = pd.Series(s, index=['a', 'c'])
print(s1)
</code></pre>
<p>输出：</p>
<pre><code>a    1
b    2
c    3
d    4
e    5
dtype: int64
a    1
c    3
dtype: int64
</code></pre>
<p>通过 <code>pd.Series(s, index=[...])</code> 可以从已有 Series 中筛选出指定索引对应的元素，未匹配的索引会被忽略。</p>
<hr />
<h1>14.3 Series 的属性</h1>
<p>Series 提供了丰富的属性来描述自身的特征：</p>
<table>
<thead>
<tr>
<th>属性</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>index</code></td>
<td>Series 的索引对象</td>
</tr>
<tr>
<td><code>values</code></td>
<td>Series 的值</td>
</tr>
<tr>
<td><code>dtype</code> 或 <code>dtypes</code></td>
<td>Series 的元素类型</td>
</tr>
<tr>
<td><code>shape</code></td>
<td>Series 的形状</td>
</tr>
<tr>
<td><code>ndim</code></td>
<td>Series 的维度</td>
</tr>
<tr>
<td><code>size</code></td>
<td>Series 的元素个数</td>
</tr>
<tr>
<td><code>name</code></td>
<td>Series 的名称</td>
</tr>
<tr>
<td><code>loc[]</code></td>
<td>显式索引，按标签索引或切片</td>
</tr>
<tr>
<td><code>iloc[]</code></td>
<td>隐式索引，按位置索引或切片</td>
</tr>
<tr>
<td><code>at[]</code></td>
<td>使用标签访问单个元素</td>
</tr>
<tr>
<td><code>iat[]</code></td>
<td>使用位置访问单个元素</td>
</tr>
</tbody>
</table>
<p><strong>访问属性示例：</strong></p>
<pre><code>s = pd.Series({'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5})
s.name = 'test'
print(s.index)
print(s.values)
print(s.shape, s.ndim, s.size)
print(s.dtype, s.name)
</code></pre>
<p>输出：</p>
<pre><code>Index(['a', 'b', 'c', 'd', 'e'], dtype='object')
[1 2 3 4 5]
(5,) 1 5
int64 test
</code></pre>
<hr />
<h1>14.4 索引与访问</h1>
<p>Pandas 提供了多种索引方式，分为<strong>显式索引</strong>（按标签）和<strong>隐式索引</strong>（按位置）。</p>
<pre><code>print(s.loc['a'])   # 显式索引，按标签访问
print(s.iloc[0])    # 隐式索引，按位置访问
print(s.at['a'])    # 按标签访问单个元素（不支持切片）
print(s.iat[0])     # 按位置访问单个元素（不支持切片）
</code></pre>
<p>输出：</p>
<pre><code>1
1
1
1
</code></pre>
<p><strong>布尔索引：</strong></p>
<pre><code>print(s[s &lt; 3])  # 筛选值小于3的元素
</code></pre>
<p>输出：</p>
<pre><code>a    1
b    2
Name: test, dtype: int64
</code></pre>
<p><strong>添加元素与预览：</strong></p>
<pre><code>s['f'] = 6            # 通过标签添加新元素
print(s.head())        # 返回前5行
print(s.tail())        # 返回后5行
print(s.head(2))       # 返回前2行
</code></pre>
<p>输出：</p>
<pre><code>a    1
b    2
c    3
d    4
e    5
Name: test, dtype: int64
b    2
c    3
d    4
e    5
f    6
Name: test, dtype: int64
a    1
b    2
Name: test, dtype: int64
</code></pre>
<p><strong>要点说明：</strong></p>
<ul>
<li><code>loc[]</code> 按<strong>标签名</strong>访问（如 <code>s.loc['a']</code>），支持切片（左右均包含）。</li>
<li><code>iloc[]</code> 按<strong>位置序号</strong>访问（如 <code>s.iloc[0]</code>），切片规则与 Python 一致（左闭右开）。</li>
<li><code>at[]</code> / <code>iat[]</code> 仅用于访问<strong>单个元素</strong>，速度比 <code>loc</code> / <code>iloc</code> 更快。</li>
<li>不推荐使用 <code>s[0]</code> 直接访问，因为索引不清楚是显式还是隐式，容易产生歧义。</li>
</ul>
<hr />
<h1>14.5 本节小结</h1>
<p>本节完成了以下内容：</p>
<ul>
<li>理解了 Series 的一维数组结构及其三要素：索引、名称、值。</li>
<li>掌握了 Series 的四种创建方式：列表（默认索引）、自定义索引、指定名称、字典创建。</li>
<li>熟悉了 Series 的常用属性：<code>index</code>、<code>values</code>、<code>dtype</code>、<code>shape</code>、<code>ndim</code>、<code>size</code>、<code>name</code>。</li>
<li>掌握了显式索引（<code>loc</code>、<code>at</code>）与隐式索引（<code>iloc</code>、<code>iat</code>）的区别与用法。</li>
<li>学会了布尔索引筛选、元素添加、<code>head()</code> 与 <code>tail()</code> 预览等实用操作。</li>
</ul>
<p>下一节我们将学习 <strong>Series 的常用方法</strong>。</p>
<hr />
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="信息学"/>
  </entry>
  <entry>
    <title>数据分析｜Series的常用方法</title>
    <link href="https://luq-blog.xyz/posts/2026-07-03-data-notes-15-series-methods/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-07-03-data-notes-15-series-methods/</id>
    <published>2026-07-03T00:00:00.000Z</published>
    <updated>2026-07-05T00:00:00.000Z</updated>
    <summary>本节系统介绍 Pandas Series 的常用方法，涵盖统计描述、缺失值判断、去重排序、索引操作，以及案例中涉及的补充方法。</summary>
    <content type="html"><![CDATA[<h1>15.1 Series 常用方法概述</h1>
<p>Pandas Series 提供了丰富的方法用于数据统计、清洗和操作。下表按功能分类列出：</p>
<h2>常用方法总览</h2>
<table>
<thead>
<tr>
<th>方法</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>head()</code></td>
<td>查看前 n 行数据，默认 5 行</td>
</tr>
<tr>
<td><code>tail()</code></td>
<td>查看后 n 行数据，默认 5 行</td>
</tr>
<tr>
<td><code>isin()</code></td>
<td>判断元素是否包含在参数集合中</td>
</tr>
<tr>
<td><code>isna()</code></td>
<td>判断是否为缺失值（如 <code>NaN</code> 或 <code>None</code>）</td>
</tr>
<tr>
<td><code>sum()</code></td>
<td>求和，自动忽略缺失值</td>
</tr>
<tr>
<td><code>mean()</code></td>
<td>平均值</td>
</tr>
<tr>
<td><code>min()</code></td>
<td>最小值</td>
</tr>
<tr>
<td><code>max()</code></td>
<td>最大值</td>
</tr>
<tr>
<td><code>var()</code></td>
<td>方差</td>
</tr>
<tr>
<td><code>std()</code></td>
<td>标准差</td>
</tr>
<tr>
<td><code>median()</code></td>
<td>中位数</td>
</tr>
<tr>
<td><code>mode()</code></td>
<td>众数（可返回多个）</td>
</tr>
<tr>
<td><code>quantile(q)</code></td>
<td>分位数，q 取 0~1 之间</td>
</tr>
<tr>
<td><code>describe()</code></td>
<td>常见统计信息（count、mean、std、min、25%、50%、75%、max）</td>
</tr>
<tr>
<td><code>value_counts()</code></td>
<td>每个唯一值的出现次数</td>
</tr>
<tr>
<td><code>count()</code></td>
<td>非缺失值数量</td>
</tr>
<tr>
<td><code>nunique()</code></td>
<td>唯一值个数（去重）</td>
</tr>
<tr>
<td><code>unique()</code></td>
<td>获取去重后的值数组</td>
</tr>
<tr>
<td><code>drop_duplicates()</code></td>
<td>去除重复项</td>
</tr>
<tr>
<td><code>sample()</code></td>
<td>随机抽样</td>
</tr>
<tr>
<td><code>sort_index()</code></td>
<td>按索引排序</td>
</tr>
<tr>
<td><code>sort_values()</code></td>
<td>按值排序</td>
</tr>
<tr>
<td><code>replace()</code></td>
<td>替换值</td>
</tr>
<tr>
<td><code>keys()</code></td>
<td>返回 Series 的索引对象</td>
</tr>
</tbody>
</table>
<hr />
<h1>15.2 统计与描述方法</h1>
<p>以下示例基于一个包含缺失值的 Series：</p>
<pre><code>import pandas as pd
import numpy as np

s = pd.Series([10, 2, np.nan, None, 3, 4, 5],
              index=['A', 'B', 'C', 'D', 'E', 'F', 'G'],
              name='data')
print(s)
</code></pre>
<p>输出：</p>
<pre><code>A    10.0
B     2.0
C     NaN
D     NaN
E     3.0
F     4.0
G     5.0
Name: data, dtype: float64
</code></pre>
<h2>15.2.1 数据预览</h2>
<pre><code>print(s.head())   # 默认取前5行
print(s.tail())   # 默认取后5行
</code></pre>
<p>输出：</p>
<pre><code>A    10.0
B     2.0
C     NaN
D     NaN
E     3.0
Name: data, dtype: float64
C     NaN
D     NaN
E     3.0
F     4.0
G     5.0
Name: data, dtype: float64
</code></pre>
<h2>15.2.2 描述性统计</h2>
<pre><code>print(s.describe())
</code></pre>
<p>输出：</p>
<pre><code>count     5.000000
mean      4.800000
std       3.114482
min       2.000000
25%       3.000000
50%       4.000000
75%       5.000000
max      10.000000
Name: data, dtype: float64
</code></pre>
<p><code>describe()</code> 一次性返回 count（非缺失值数量）、mean、std、min、四分位数和 max，是数据探索的第一步常用操作。</p>
<h2>15.2.3 常用统计计算</h2>
<pre><code>print(s.mean())     # 平均值
print(s.sum())      # 总和
print(s.std())      # 标准差
print(s.var())      # 方差
print(s.min())      # 最小值
print(s.max())      # 最大值
print(s.median())   # 中位数
print(s.quantile(0.25))  # 第25百分位数
</code></pre>
<p>输出：</p>
<pre><code>4.8
24.0
3.1144823004794877
9.700000000000001
2.0
10.0
4.0
3.0
</code></pre>
<h2>15.2.4 众数</h2>
<pre><code>s['H'] = 4
print(s.mode())
</code></pre>
<p>输出：</p>
<pre><code>0    4.0
Name: data, dtype: float64
</code></pre>
<p><code>mode()</code> 返回众数，可能有多个值，返回一个 Series。</p>
<h2>15.2.5 缺失值判断</h2>
<pre><code>print(s.isna())   # 判断每个元素是否为缺失值
</code></pre>
<p>输出：</p>
<pre><code>A    False
B    False
C     True
D     True
E    False
F    False
G    False
H    False
Name: data, dtype: bool
</code></pre>
<pre><code>print(s.count())  # 非缺失值数量
</code></pre>
<p>输出：</p>
<pre><code>6
</code></pre>
<h2>15.2.6 元素包含判断</h2>
<pre><code>print(s.isin([4, 5, 6]))  # 判断元素是否在指定集合中
</code></pre>
<p>输出：</p>
<pre><code>A    False
B    False
C    False
D    False
E    False
F     True
G     True
H     True
Name: data, dtype: bool
</code></pre>
<hr />
<h1>15.3 去重、排序与索引方法</h1>
<h2>15.3.1 唯一值与去重</h2>
<pre><code>print(s.value_counts())       # 每个唯一值的出现次数
print(s.drop_duplicates())    # 去重（返回 Series）
print(s.unique())             # 去重（返回 numpy 数组）
print(s.nunique())            # 去重后的元素个数
</code></pre>
<p>输出：</p>
<pre><code>data
4.0     2
10.0    1
2.0     1
3.0     1
5.0     1
Name: count, dtype: int64

A    10.0
B     2.0
C     NaN
E     3.0
F     4.0
G     5.0
Name: data, dtype: float64

[10.  2. nan  3.  4.  5.]

5
</code></pre>
<p><strong>要点说明：</strong></p>
<ul>
<li><code>value_counts()</code> 用于统计频次，默认按出现次数降序排列。</li>
<li><code>drop_duplicates()</code> 返回去重后的 Series，保留原始索引。</li>
<li><code>unique()</code> 返回去重后的 numpy 数组。</li>
<li><code>nunique()</code> 返回去重后的个数（整数）。</li>
</ul>
<h2>15.3.2 排序</h2>
<pre><code>print(s.sort_index())    # 按索引排序
print(s.sort_values())   # 按值排序（缺失值默认排在末尾）
</code></pre>
<p>输出：</p>
<pre><code>A    10.0
B     2.0
C     NaN
D     NaN
E     3.0
F     4.0
G     5.0
H     4.0
Name: data, dtype: float64

B     2.0
E     3.0
F     4.0
H     4.0
G     5.0
A    10.0
C     NaN
D     NaN
Name: data, dtype: float64
</code></pre>
<h2>15.3.3 索引对象</h2>
<pre><code>print(s.keys())   # 返回索引对象
print(s.index)    # 属性方式获取索引
</code></pre>
<p>输出：</p>
<pre><code>Index(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'], dtype='object')
Index(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'], dtype='object')
</code></pre>
<hr />
<h1>15.4 案例中涉及的补充方法</h1>
<p>以下方法在实际数据分析案例中出现，作为补充参考：</p>
<table>
<thead>
<tr>
<th>方法</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>diff()</code></td>
<td>计算相邻元素之间的差值，常用于分析变化量</td>
</tr>
<tr>
<td><code>pct_change()</code></td>
<td>计算相邻元素之间的百分比变化率，常用于收益率、环比分析</td>
</tr>
<tr>
<td><code>idxmax()</code></td>
<td>返回最大值的索引标签</td>
</tr>
<tr>
<td><code>idxmin()</code></td>
<td>返回最小值的索引标签</td>
</tr>
<tr>
<td><code>rolling(n)</code></td>
<td>生成滚动窗口对象，配合 <code>sum()</code>、<code>mean()</code> 等进行滑动计算</td>
</tr>
<tr>
<td><code>resample(rule)</code></td>
<td>按指定频率重采样时间序列数据（如按天聚合到按月）</td>
</tr>
<tr>
<td><code>abs()</code></td>
<td>对每个元素取绝对值，常配合 <code>diff()</code> 使用获取变化幅度</td>
</tr>
<tr>
<td><code>tolist()</code></td>
<td>将 Series 的索引或值转换为 Python 列表</td>
</tr>
</tbody>
</table>
<hr />
<h1>15.5 本节小结</h1>
<p>本节完成了以下内容：</p>
<ul>
<li>掌握了 Series 的统计描述方法：<code>describe()</code>、<code>mean()</code>、<code>sum()</code>、<code>std()</code>、<code>var()</code>、<code>min()</code>、<code>max()</code>、<code>median()</code>、<code>mode()</code>、<code>quantile()</code>。</li>
<li>掌握了缺失值判断方法：<code>isna()</code>、<code>count()</code>。</li>
<li>掌握了元素判断与过滤：<code>isin()</code>。</li>
<li>掌握了去重相关方法：<code>value_counts()</code>、<code>drop_duplicates()</code>、<code>unique()</code>、<code>nunique()</code>。</li>
<li>掌握了排序方法：<code>sort_index()</code>、<code>sort_values()</code>。</li>
<li>了解了案例中涉及的补充方法：<code>diff()</code>、<code>pct_change()</code>、<code>idxmax()</code>、<code>idxmin()</code>、<code>rolling()</code>、<code>resample()</code>、<code>abs()</code>、<code>tolist()</code>。</li>
</ul>
<p>下一节我们将通过 <strong>Series 案例</strong> 综合练习所学方法。</p>
<hr />
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="信息学"/>
  </entry>
  <entry>
    <title>数据分析｜Series案例</title>
    <link href="https://luq-blog.xyz/posts/2026-07-03-data-notes-16-series-cases/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-07-03-data-notes-16-series-cases/</id>
    <published>2026-07-03T00:00:00.000Z</published>
    <updated>2026-07-03T00:00:00.000Z</updated>
    <summary>本节通过学生成绩、温度、股票、销售等实际案例，综合运用 Series 的创建、统计、筛选、排序、重采样等方法，巩固前几节所学知识。</summary>
    <content type="html"><![CDATA[<h1>16.1 案例概述</h1>
<p>本节通过五个实际案例，综合运用 Series 的创建、筛选、统计、排序等方法，巩固前几节所学知识。</p>
<p>使用的库：</p>
<pre><code>import numpy as np
import pandas as pd
</code></pre>
<hr />
<h1>16.2 学生成绩统计</h1>
<p>生成 10 名学生的随机成绩，进行基本统计分析。</p>
<pre><code>np.random.seed(42)
scores = pd.Series(np.random.randint(50, 101, 10),
                   index=['学生' + str(i) for i in range(1, 11)])
print(scores)
</code></pre>
<p>输出：</p>
<pre><code>学生1     88
学生2     78
学生3     64
学生4     92
学生5     57
学生6     70
学生7     88
学生8     68
学生9     72
学生10    60
dtype: int32
</code></pre>
<pre><code>print('平均分:', scores.mean())
print('最高分:', scores.max())
print('最低分:', scores.min())
print('高于平均分的人数:', scores[scores &gt; scores.mean()].count())
</code></pre>
<p>输出：</p>
<pre><code>平均分: 73.7
最高分: 92
最低分: 57
高于平均分的人数: 4
</code></pre>
<p><strong>涉及方法：</strong> <code>mean()</code>、<code>max()</code>、<code>min()</code>、布尔索引筛选、<code>count()</code>。</p>
<hr />
<h1>16.3 温度数据分析</h1>
<p>分析一周温度数据，找出最高温所在日和温度变化最大的两天。</p>
<pre><code>temperatures = pd.Series([28, 31, 29, 32, 30, 27, 33],
                          index=['周一', '周二', '周三', '周四', '周五', '周六', '周日'])
# 温度超过30的天数
print('超30℃天数:', temperatures[temperatures &gt; 30].count())
print('平均温度:', temperatures.mean())

# 按温度降序排列
print(temperatures.sort_values(ascending=False))
</code></pre>
<p>输出：</p>
<pre><code>超30℃天数: 3
平均温度: 30.0
周日    33
周四    32
周二    31
周五    30
周三    29
周一    28
周六    27
dtype: int64
</code></pre>
<pre><code># 计算温度变化最大的两天
# 原始温度: 28  31  29  32  30  27  33
# 相邻差:   nan  3  -2   3  -2  -3   6
t3 = temperatures.diff().abs()
print('温度变化最大的2天:', t3.sort_values(ascending=False).index[:2].tolist())
</code></pre>
<p>输出：</p>
<pre><code>温度变化最大的2天: ['周日', '周二']
</code></pre>
<p><strong>分析过程：</strong></p>
<ol>
<li><code>diff()</code>：计算相邻两天的温差。</li>
<li><code>abs()</code>：取绝对值，获取变化幅度（不分正负）。</li>
<li><code>sort_values(ascending=False)</code>：按变化幅度降序排列。</li>
<li><code>index[:2].tolist()</code>：取前两名的索引（周几）并转为列表。</li>
</ol>
<hr />
<h1>16.4 股票价格分析</h1>
<p>模拟 10 天的股票收盘价，计算每日收益率和波动率。</p>
<pre><code>prices = pd.Series([102.3, 103.5, 105.1, 104.8, 106.2, 107.0,
                    106.5, 108.1, 109.3, 110.2],
                   index=pd.date_range('2023-01-01', periods=10))
print(prices)
</code></pre>
<p>输出：</p>
<pre><code>2023-01-01    102.3
2023-01-02    103.5
2023-01-03    105.1
2023-01-04    104.8
2023-01-05    106.2
2023-01-06    107.0
2023-01-07    106.5
2023-01-08    108.1
2023-01-09    109.3
2023-01-10    110.2
Freq: D, dtype: float64
</code></pre>
<p><code>pd.date_range()</code> 可以快速生成连续的日期索引，<code>periods</code> 指定生成天数。</p>
<pre><code># 每日收益率 = 当日收盘价 / 前日收盘价 - 1
a = prices.pct_change()
print(a)
</code></pre>
<p>输出：</p>
<pre><code>2023-01-01         NaN
2023-01-02    0.011730
2023-01-03    0.015459
2023-01-04   -0.002854
2023-01-05    0.013359
2023-01-06    0.007533
2023-01-07   -0.004673
2023-01-08    0.015023
2023-01-09    0.011101
2023-01-10    0.008234
Freq: D, dtype: float64
</code></pre>
<p>第一天为 <code>NaN</code>，因为没有前一天的数据可供计算。</p>
<pre><code>print('收益率最高的日期:', a.idxmax())
print('收益率最低的日期:', a.idxmin())
print('波动率（标准差）:', a.std())
</code></pre>
<p>输出：</p>
<pre><code>收益率最高的日期: 2023-01-03 00:00:00
收益率最低的日期: 2023-01-07 00:00:00
波动率（标准差）: 0.007373623845361105
</code></pre>
<p><strong>涉及方法：</strong> <code>pd.date_range()</code>、<code>pct_change()</code>、<code>idxmax()</code>、<code>idxmin()</code>、<code>std()</code>。</p>
<hr />
<h1>16.5 销售数据分析</h1>
<p>分析 2022 年月度销售数据，实现季度汇总和连续增长检测。</p>
<pre><code>sales = pd.Series([120, 135, 145, 160, 155, 170, 180, 175, 190, 200, 210, 220],
                  index=pd.date_range('2022-01-01', periods=12, freq='MS'))
print(sales)
</code></pre>
<p>输出：</p>
<pre><code>2022-01-01    120
2022-02-01    135
2022-03-01    145
2022-04-01    160
2022-05-01    155
2022-06-01    170
2022-07-01    180
2022-08-01    175
2022-09-01    190
2022-10-01    200
2022-11-01    210
2022-12-01    220
Freq: MS, dtype: int64
</code></pre>
<p><code>freq='MS'</code> 表示月初（Month Start），生成每月第一天。</p>
<p><strong>季度平均销量：</strong></p>
<pre><code>print(sales.resample('QS').mean())
</code></pre>
<p>输出：</p>
<pre><code>2022-01-01    133.333333
2022-04-01    161.666667
2022-07-01    181.666667
2022-10-01    210.000000
Freq: QS-JAN, dtype: float64
</code></pre>
<p><code>resample('QS')</code> 按季度（Quarter Start）对时间序列做重采样，配合 <code>mean()</code> 计算每季度平均值。</p>
<p><strong>月环比增长率：</strong></p>
<pre><code>print(sales.pct_change())
</code></pre>
<p>输出：</p>
<pre><code>2022-01-01         NaN
2022-02-01    0.125000
2022-03-01    0.074074
2022-04-01    0.103448
2022-05-01   -0.031250
2022-06-01    0.096774
2022-07-01    0.058824
2022-08-01   -0.027778
2022-09-01    0.085714
2022-10-01    0.052632
2022-11-01    0.050000
2022-12-01    0.047619
Freq: MS, dtype: float64
</code></pre>
<p><strong>连续增长超过 2 个月的月份（滑动窗口算法）：</strong></p>
<pre><code>a = sales.pct_change()
b = a &gt; 0
print(list(b[b.rolling(3).sum() == 3].keys()))
</code></pre>
<p>输出：</p>
<pre><code>[Timestamp('2022-04-01 00:00:00'), Timestamp('2022-11-01 00:00:00'), Timestamp('2022-12-01 00:00:00')]
</code></pre>
<p><strong>分析过程：</strong></p>
<ol>
<li><code>pct_change()</code>：计算逐月环比增长率。</li>
<li><code>&gt; 0</code>：生成布尔 Series，标记增长（True）或下降（False）。</li>
<li><code>rolling(3).sum()</code>：滑动窗口大小为 3，计算连续 3 个月中增长的月份数。</li>
<li><code>== 3</code>：筛选出连续 3 个月都增长的月份。</li>
</ol>
<p><strong>涉及方法：</strong> <code>resample()</code>、<code>pct_change()</code>、<code>idxmax()</code>、<code>rolling()</code>、<code>keys()</code>。</p>
<hr />
<h1>16.6 每小时销售数据分析</h1>
<p>生成一天 24 小时的随机销售数据，按天聚合计算总销售额。</p>
<pre><code>np.random.seed(42)
hourly_sales = pd.Series(np.random.randint(1, 100, 24),
                         index=pd.date_range('2025-01-01', periods=24, freq='h'))
print(hourly_sales)
</code></pre>
<p>输出：</p>
<pre><code>2025-01-01 00:00:00    52
2025-01-01 01:00:00    93
2025-01-01 02:00:00    15
2025-01-01 03:00:00    72
2025-01-01 04:00:00    61
2025-01-01 05:00:00    21
2025-01-01 06:00:00    83
2025-01-01 07:00:00    87
2025-01-01 08:00:00    75
2025-01-01 09:00:00    75
2025-01-01 10:00:00    88
2025-01-01 11:00:00    24
2025-01-01 12:00:00     3
2025-01-01 13:00:00    22
2025-01-01 14:00:00    53
2025-01-01 15:00:00     2
2025-01-01 16:00:00    88
2025-01-01 17:00:00    30
2025-01-01 18:00:00    38
2025-01-01 19:00:00     2
2025-01-01 20:00:00    64
2025-01-01 21:00:00    60
2025-01-01 22:00:00    21
2025-01-01 23:00:00    33
Freq: h, dtype: int32
</code></pre>
<p><code>freq='h'</code> 表示按小时生成时间序列。</p>
<p><strong>按天汇总总销售额：</strong></p>
<pre><code>print(hourly_sales.resample('D').sum())
</code></pre>
<p>输出：</p>
<pre><code>2025-01-01    1162
Freq: D, dtype: int32
</code></pre>
<p><code>resample('D')</code> 将 24 条小时数据聚合为 1 条日数据，配合 <code>sum()</code> 计算当天总销售额。</p>
<hr />
<h1>16.7 本节小结</h1>
<p>本节通过五个案例综合运用了 Series 的核心方法：</p>
<table>
<thead>
<tr>
<th>案例</th>
<th>涉及方法</th>
</tr>
</thead>
<tbody>
<tr>
<td>学生成绩统计</td>
<td><code>mean()</code>、<code>max()</code>、<code>min()</code>、布尔索引、<code>count()</code></td>
</tr>
<tr>
<td>温度数据分析</td>
<td><code>sort_values()</code>、<code>diff()</code>、<code>abs()</code>、<code>tolist()</code></td>
</tr>
<tr>
<td>股票价格分析</td>
<td><code>pd.date_range()</code>、<code>pct_change()</code>、<code>idxmax()</code>、<code>idxmin()</code>、<code>std()</code></td>
</tr>
<tr>
<td>销售数据分析</td>
<td><code>resample()</code>、<code>pct_change()</code>、<code>rolling()</code>、<code>keys()</code></td>
</tr>
<tr>
<td>每小时销售分析</td>
<td><code>pd.date_range(freq='h')</code>、<code>resample('D').sum()</code></td>
</tr>
</tbody>
</table>
<p>下一节我们将学习 <strong>DataFrame 的介绍和创建</strong>。</p>
<hr />
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="信息学"/>
  </entry>
  <entry>
    <title>数据分析｜比较函数</title>
    <link href="https://luq-blog.xyz/posts/2026-07-02-data-notes-10-comparison/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-07-02-data-notes-10-comparison/</id>
    <published>2026-07-02T00:00:00.000Z</published>
    <updated>2026-07-02T00:00:00.000Z</updated>
    <summary>本节介绍 NumPy 中的比较函数，包括逐元素比较、逻辑运算、条件筛选与多条件赋值，掌握数据筛选的核心技巧。</summary>
    <content type="html"><![CDATA[<h1>10.1 比较函数概述</h1>
<p>数据分析中经常需要根据条件筛选数据，NumPy 提供了一系列比较函数来实现逐元素的条件判断。下表列出了本节涉及的比较函数：</p>
<table>
<thead>
<tr>
<th>函数</th>
<th>功能</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>np.greater(a, b)</code></td>
<td>逐元素判断 a 是否大于 b</td>
</tr>
<tr>
<td><code>np.less(a, b)</code></td>
<td>逐元素判断 a 是否小于 b</td>
</tr>
<tr>
<td><code>np.equal(a, b)</code></td>
<td>逐元素判断 a 是否等于 b</td>
</tr>
<tr>
<td><code>np.logical_and(a, b)</code></td>
<td>逐元素逻辑与运算</td>
</tr>
<tr>
<td><code>np.logical_or(a, b)</code></td>
<td>逐元素逻辑或运算</td>
</tr>
<tr>
<td><code>np.logical_not(a)</code></td>
<td>逐元素逻辑非运算</td>
</tr>
<tr>
<td><code>np.any(a)</code></td>
<td>检查数组中是否至少有一个 <code>True</code></td>
</tr>
<tr>
<td><code>np.all(a)</code></td>
<td>检查数组中是否全部元素为 <code>True</code></td>
</tr>
<tr>
<td><code>np.where(condition, x, y)</code></td>
<td>根据条件选择元素</td>
</tr>
<tr>
<td><code>np.select(condlist, choicelist)</code></td>
<td>多条件选择元素</td>
</tr>
</tbody>
</table>
<hr />
<h1>10.2 大于、小于、等于</h1>
<p>这三个函数用于逐元素比较两个数组（或数组与标量）的大小关系，返回布尔数组。</p>
<pre><code>import numpy as np

# 是否大于
print(np.greater([3, 4, 5, 6, 7], 4))
# 是否小于
print(np.less([3, 4, 5, 6, 7, 8], 4))
# 是否等于
print(np.equal([3, 4, 5, 6, 7, 8], 4))
print(np.equal([3, 4, 5], [4, 4, 4]))
</code></pre>
<p>输出：</p>
<pre><code>[False False  True  True  True]
[ True False False False False False]
[False  True False False False False]
[False  True False]
</code></pre>
<p><strong>要点说明：</strong></p>
<ul>
<li>当第二个参数是标量时，每个元素与该标量比较，等价于使用 <code>&gt;</code>、<code>&lt;</code>、<code>==</code> 运算符。</li>
<li>当两个数组长度相同时，逐位置一一对应比较。</li>
<li>返回值是一个布尔数组，常配合布尔索引使用（参见第6节）。</li>
</ul>
<hr />
<h1>10.3 逻辑运算</h1>
<p><code>np.logical_and()</code>、<code>np.logical_or()</code> 和 <code>np.logical_not()</code> 用于对布尔数组进行逐元素的逻辑运算。</p>
<pre><code>print(np.logical_and([1, 0], [1, 1]))    # 逻辑与
print(np.logical_not([1, 0]))             # 逻辑非
print(np.logical_or([0, 0], [1, 0]))      # 逻辑或
</code></pre>
<p>输出：</p>
<pre><code>[ True False]
[False  True]
[ True False]
</code></pre>
<p><strong>要点说明：</strong></p>
<ul>
<li><code>np.logical_and()</code>：两个输入都为真时返回 <code>True</code>。</li>
<li><code>np.logical_or()</code>：至少一个输入为真时返回 <code>True</code>。</li>
<li><code>np.logical_not()</code>：对布尔值取反。</li>
<li>在布尔索引中组合多个条件时，使用 <code>&amp;</code>（与）、<code>|</code>（或）运算符（参见第6节）。</li>
</ul>
<hr />
<h1>10.4 any 与 all</h1>
<p><code>np.any()</code> 检查数组中是否<strong>至少有一个</strong> <code>True</code>，<code>np.all()</code> 检查数组中是否<strong>全部</strong>为 <code>True</code>。</p>
<pre><code># 检查是否至少有一个元素为 True
print(np.any([0, 1, 0, 0, 0]))
print(np.any([0, 0, 0, 0, 0]))

# 检查是否全部元素为 True
print(np.all([1, 1]))
print(np.all([1, 0]))
</code></pre>
<p>输出：</p>
<pre><code>True
False
True
False
</code></pre>
<p>这两个函数常用于数据验证场景，例如检查数据集中是否存在缺失值、所有样本是否满足某个条件等。</p>
<hr />
<h1>10.5 np.where()</h1>
<p><code>np.where(condition, x, y)</code> 是条件筛选的核心函数。当 <code>condition</code> 为 <code>True</code> 时取 <code>x</code> 对应的值，否则取 <code>y</code> 对应的值。</p>
<pre><code>arr = np.array([1, 2, 3, 4, 5])

print(np.where(arr &gt; 3, arr, 0))   # 大于3的保留原值，否则置0
print(np.where(arr &lt; 3, 1, 0))     # 小于3的置1，否则置0
</code></pre>
<p>输出：</p>
<pre><code>[0 0 0 4 5]
[1 1 0 0 0]
</code></pre>
<p><strong>实际应用：成绩等级评定</strong></p>
<pre><code>score = np.random.randint(50, 100, 20)
print(score)
print(np.where(score &gt;= 60, '及格', '不及格'))
</code></pre>
<p>输出（结果随机）：</p>
<pre><code>[53 89 59 69 71 86 73 56 74 74 62 51 88 89 73 96 74 67 87 75]
['不及格' '及格' '不及格' '及格' '及格' '及格' '及格' '不及格' '及格' '及格' '及格' '不及格' '及格' '及格'
 '及格' '及格' '及格' '及格' '及格' '及格']
</code></pre>
<p><strong>嵌套 np.where() 实现多级判定：</strong></p>
<pre><code>print(np.where(
    score &lt; 60, '不及格', np.where(
        score &lt; 80, '良好', '优秀'
    )
))
</code></pre>
<p>输出（结果随机）：</p>
<pre><code>['不及格' '优秀' '不及格' '良好' '良好' '优秀' '良好' '不及格' '良好' '良好' '良好' '不及格' '优秀' '优秀'
 '良好' '优秀' '良好' '良好' '优秀' '良好']
</code></pre>
<p>通过嵌套 <code>np.where()</code>，可以实现"不及格 / 良好 / 优秀"三级分类。逻辑是：先判断是否小于 60，若是则"不及格"；否则再判断是否小于 80，若是则"良好"，否则"优秀"。</p>
<hr />
<h1>10.6 np.select()</h1>
<p>当条件分支较多时，嵌套 <code>np.where()</code> 可读性较差。<code>np.select(condlist, choicelist)</code> 提供更清晰的多条件选择方式。</p>
<pre><code>print(np.select(
    [score &gt; 80, (score &gt;= 60) &amp; (score &lt;= 80), score &lt; 60],
    ['优秀', '良好', '不及格'],
    default='未知'
))
</code></pre>
<p>输出（结果随机）：</p>
<pre><code>['不及格' '优秀' '不及格' '良好' '良好' '优秀' '良好' '不及格' '良好' '良好' '良好' '不及格' '优秀' '优秀'
 '良好' '优秀' '良好' '良好' '优秀' '良好']
</code></pre>
<p><strong>要点说明：</strong></p>
<ul>
<li><code>condlist</code>：条件列表，按顺序逐一判断，命中的第一个条件对应的值被选中。</li>
<li><code>choicelist</code>：返回值列表，与条件列表一一对应。</li>
<li><code>default</code>：当所有条件都不满足时的兜底值。</li>
<li>条件按顺序匹配（类似 <code>if-elif-else</code>），排在前面的条件优先级更高。</li>
</ul>
<hr />
<h1>10.7 本节小结</h1>
<p>本节完成了以下内容：</p>
<ul>
<li>掌握了 <code>np.greater()</code>、<code>np.less()</code>、<code>np.equal()</code> 逐元素比较函数。</li>
<li>掌握了 <code>np.logical_and()</code>、<code>np.logical_or()</code>、<code>np.logical_not()</code> 逻辑运算。</li>
<li>掌握了 <code>np.any()</code> 和 <code>np.all()</code> 的数组整体真值判断。</li>
<li>掌握了 <code>np.where()</code> 条件筛选与嵌套多级判定。</li>
<li>掌握了 <code>np.select()</code> 多条件选择及其 <code>default</code> 参数。</li>
</ul>
<p>下一节我们将学习 NumPy 中的<strong>排序函数</strong>，包括排序、去重、数组拼接与分割。</p>
<hr />
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="信息学"/>
  </entry>
  <entry>
    <title>数据分析｜排序函数</title>
    <link href="https://luq-blog.xyz/posts/2026-07-02-data-notes-11-sort/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-07-02-data-notes-11-sort/</id>
    <published>2026-07-02T00:00:00.000Z</published>
    <updated>2026-07-02T00:00:00.000Z</updated>
    <summary>本节介绍 NumPy 中的排序函数，包括数组排序、索引排序、去重与成员检测，掌握数据整理的核心方法。</summary>
    <content type="html"><![CDATA[<h1>11.1 排序函数概述</h1>
<p>在数据分析中，排序和去重是数据预处理的基本操作。NumPy 提供了高效的排序函数，下表列出了本节涉及的主要函数：</p>
<table>
<thead>
<tr>
<th>函数</th>
<th>功能</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>np.sort(x)</code></td>
<td>返回排序后的新数组（原数组不变）</td>
</tr>
<tr>
<td><code>x.sort()</code></td>
<td>原地排序，直接修改原数组</td>
</tr>
<tr>
<td><code>np.argsort(x)</code></td>
<td>返回排序后各元素在原数组中的索引</td>
</tr>
<tr>
<td><code>np.lexsort(keys)</code></td>
<td>根据多个键进行间接排序</td>
</tr>
<tr>
<td><code>np.unique(x)</code></td>
<td>返回去重并排序后的唯一值</td>
</tr>
<tr>
<td><code>np.in1d(a, b)</code></td>
<td>检查 a 中的元素是否存在于 b 中</td>
</tr>
</tbody>
</table>
<hr />
<h1>11.2 np.sort() 与 arr.sort()</h1>
<p><code>np.sort()</code> 返回排序后的<strong>新数组</strong>，原数组保持不变；<code>arr.sort()</code> 是数组的<strong>方法</strong>，会原地修改原数组。</p>
<pre><code>import numpy as np

np.random.seed(0)
arr = np.random.randint(1, 100, 20)
print(arr)
</code></pre>
<p>输出：</p>
<pre><code>[45 48 65 68 68 10 84 22 37 88 71 89 89 13 59 66 40 88 47 89]
</code></pre>
<pre><code># np.sort() 返回新数组，原数组不变
print(np.sort(arr))
print(arr)  # 原数组不变
</code></pre>
<p>输出：</p>
<pre><code>[10 13 22 37 40 45 47 48 59 65 66 68 68 71 84 88 88 89 89 89]
[45 48 65 68 68 10 84 22 37 88 71 89 89 13 59 66 40 88 47 89]
</code></pre>
<pre><code># arr.sort() 原地排序，修改原数组
arr.sort()
print(arr)  # 原数组已被修改
</code></pre>
<p>输出：</p>
<pre><code>[10 13 22 37 40 45 47 48 59 65 66 68 68 71 84 88 88 89 89 89]
</code></pre>
<p><strong>选择建议：</strong></p>
<ul>
<li>需要保留原始数据顺序时，使用 <code>np.sort()</code>。</li>
<li>不需要保留原始数据且希望节省内存时，使用 <code>arr.sort()</code>。</li>
</ul>
<hr />
<h1>11.3 np.argsort()</h1>
<p><code>np.argsort()</code> 不返回排序后的值，而是返回<strong>排序后各元素在原数组中的索引位置</strong>。这在需要按一个数组的顺序排列另一个数组时非常有用。</p>
<pre><code>np.random.seed(0)
arr = np.random.randint(1, 100, 20)
print(arr)
print(np.argsort(arr))
</code></pre>
<p>输出：</p>
<pre><code>[45 48 65 68 68 10 84 22 37 88 71 89 89 13 59 66 40 88 47 89]
[ 5 13  7  8 16  0 18  1 14  2 15  3  4 10  6 17  9 11 12 19]
</code></pre>
<p><strong>结果解读：</strong></p>
<ul>
<li><code>argsort</code> 返回值的第一个元素是 <code>5</code>，表示排序后最小的元素（10）在原数组的索引 5 位置。</li>
<li>验证：<code>arr[5]</code> = 10，确实是最小值。</li>
<li><code>arr[argsort]</code> 等价于 <code>np.sort(arr)</code>。</li>
</ul>
<hr />
<h1>11.4 np.unique() 去重</h1>
<p><code>np.unique()</code> 返回数组中<strong>去重并排序</strong>后的唯一值。</p>
<pre><code>np.random.seed(0)
arr = np.random.randint(1, 100, 20)
print(arr)
print(np.unique(arr))
</code></pre>
<p>输出：</p>
<pre><code>[45 48 65 68 68 10 84 22 37 88 71 89 89 13 59 66 40 88 47 89]
[10 13 22 37 40 45 47 48 59 65 66 68 71 84 88 89]
</code></pre>
<p>原始数组有 20 个元素，去重后剩下 16 个唯一值，且默认按升序排列。在数据分析中，<code>np.unique()</code> 常用于统计类别、去除重复样本等场景。</p>
<hr />
<h1>11.5 本节小结</h1>
<p>本节完成了以下内容：</p>
<ul>
<li>掌握了 <code>np.sort()</code>（返回新数组）与 <code>arr.sort()</code>（原地排序）的区别与适用场景。</li>
<li>掌握了 <code>np.argsort()</code> 返回排序索引的用法及其解读方式。</li>
<li>掌握了 <code>np.unique()</code> 去重并排序的功能。</li>
</ul>
<p>下一节我们将对 NumPy 章节进行<strong>整体回顾与小结</strong>，梳理本章的核心知识点。</p>
<hr />
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="信息学"/>
  </entry>
  <entry>
    <title>数据分析｜本章小结及补充</title>
    <link href="https://luq-blog.xyz/posts/2026-07-02-data-notes-12-numpy-summary/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-07-02-data-notes-12-numpy-summary/</id>
    <published>2026-07-02T00:00:00.000Z</published>
    <updated>2026-07-05T00:00:00.000Z</updated>
    <summary>对 NumPy 章节进行全面回顾，梳理 ndarray 的特性与属性、创建方法、数据类型、索引切片、运算、数学函数、统计函数、比较函数与排序函数等核心知识点。</summary>
    <content type="html"><![CDATA[<h1>12.1 NumPy 介绍</h1>
<p>NumPy 是 Python 中科学计算的基础包。它是一个 Python 库，提供多维数组对象、各种派生对象（例如掩码数组和矩阵）以及用于对数组进行快速操作的各种方法，包括数学、逻辑、形状操作、排序、选择、I/O、离散傅里叶变换、基本线性代数、基本统计运算、随机模拟等等。</p>
<p>NumPy 的核心功能：</p>
<ul>
<li><code>ndarray</code>，一个具有矢量算术运算和复杂广播能力的快速且节省空间的多维数组。</li>
<li>用于对整组数据进行快速运算的标准数学函数（无需编写循环）。</li>
<li>用于读写磁盘数据的工具以及用于操作内存映射文件的工具。</li>
<li>线性代数、随机数生成以及傅里叶变换功能。</li>
<li>用于集成由 C、C++、Fortran 等语言编写的代码的 API。</li>
</ul>
<hr />
<h1>12.2 ndarray</h1>
<h2>12.2.1 NumPy 数组（ndarray）的核心特性</h2>
<ol>
<li><strong>多维性</strong>：支持 0 维（标量）、1 维（向量）、2 维（矩阵）及更高维数组。</li>
<li><strong>同质性</strong>：所有元素类型必须一致（通过 <code>dtype</code> 指定）。</li>
<li><strong>高效性</strong>：基于连续内存块存储，支持向量化运算。</li>
</ol>
<h2>12.2.2 ndarray 常用属性</h2>
<table>
<thead>
<tr>
<th>属性名称</th>
<th>通俗解释</th>
<th>使用示例</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>shape</code></td>
<td>数组的形状：行数和列数（或更高维度的尺寸）</td>
<td><code>arr.shape</code></td>
</tr>
<tr>
<td><code>ndim</code></td>
<td>维度数量：数组是几维的（1 维、2 维、3 维等）</td>
<td><code>arr.ndim</code></td>
</tr>
<tr>
<td><code>size</code></td>
<td>总元素个数：数组中所有元素的总数</td>
<td><code>arr.size</code></td>
</tr>
<tr>
<td><code>dtype</code></td>
<td>元素类型：数组中元素的类型（整数、浮点数等）</td>
<td><code>arr.dtype</code></td>
</tr>
<tr>
<td><code>T</code></td>
<td>转置：行变列，列变行</td>
<td><code>arr.T</code></td>
</tr>
<tr>
<td><code>itemsize</code></td>
<td>单个元素占用的内存字节数</td>
<td><code>arr.itemsize</code></td>
</tr>
<tr>
<td><code>nbytes</code></td>
<td>数组总内存占用量：<code>size * itemsize</code></td>
<td><code>arr.nbytes</code></td>
</tr>
<tr>
<td><code>flags</code></td>
<td>内存存储方式：是否连续存储（高级优化）</td>
<td><code>arr.flags</code></td>
</tr>
</tbody>
</table>
<hr />
<h1>12.3 ndarray 的创建</h1>
<h2>12.3.1 六大创建分类说明</h2>
<ol>
<li><strong>基础构造</strong>：适用于手动构建小规模数组或复制已有数据。</li>
<li><strong>预定义形状填充</strong>：用于快速初始化固定形状的数组（如全 0 占位、全 1 初始化）。</li>
<li><strong>基于数值范围生成</strong>：生成数值序列，常用于模拟时间序列、坐标网格等。</li>
<li><strong>特殊矩阵生成</strong>：数学运算专用（如线性代数中的单位矩阵）。</li>
<li><strong>随机数组生成</strong>：模拟实验数据、初始化神经网络权重等场景。</li>
<li><strong>高级构造方法</strong>：处理非结构化数据（如文件、字符串）或通过函数生成复杂数组。</li>
</ol>
<h2>12.3.2 创建方法对照表</h2>
<table>
<thead>
<tr>
<th>用途</th>
<th>方法</th>
</tr>
</thead>
<tbody>
<tr>
<td>基础构造</td>
<td><code>np.array()</code>、<code>np.copy()</code></td>
</tr>
<tr>
<td>预定义形状填充</td>
<td><code>np.zeros()</code>、<code>np.ones()</code>、<code>np.empty()</code>、<code>np.full()</code></td>
</tr>
<tr>
<td>基于数值范围生成</td>
<td><code>np.arange()</code>、<code>np.linspace()</code>、<code>np.logspace()</code></td>
</tr>
<tr>
<td>特殊矩阵生成</td>
<td><code>np.eye()</code>、<code>np.diag()</code></td>
</tr>
<tr>
<td>随机数组生成</td>
<td><code>np.random.rand()</code>、<code>np.random.randn()</code>、<code>np.random.randint()</code></td>
</tr>
<tr>
<td>高级构造方法</td>
<td><code>np.array()</code>、<code>np.loadtxt()</code>、<code>np.fromfunction()</code></td>
</tr>
</tbody>
</table>
<hr />
<h1>12.4 ndarray 的数据类型</h1>
<table>
<thead>
<tr>
<th>数据类型</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>bool</code></td>
<td>布尔类型</td>
</tr>
<tr>
<td><code>int8</code>、<code>uint8</code>&lt;br&gt;<code>int16</code>、<code>uint16</code>&lt;br&gt;<code>int32</code>、<code>uint32</code>&lt;br&gt;<code>int64</code>、<code>uint64</code></td>
<td>有符号、无符号的 8 位（1 字节）整型&lt;br&gt;有符号、无符号的 16 位（2 字节）整型&lt;br&gt;有符号、无符号的 32 位（4 字节）整型&lt;br&gt;有符号、无符号的 64 位（8 字节）整型</td>
</tr>
<tr>
<td><code>float16</code>&lt;br&gt;<code>float32</code>&lt;br&gt;<code>float64</code></td>
<td>半精度浮点型&lt;br&gt;单精度浮点型&lt;br&gt;双精度浮点型</td>
</tr>
<tr>
<td><code>complex64</code>&lt;br&gt;<code>complex128</code></td>
<td>用两个 32 位浮点数表示的复数&lt;br&gt;用两个 64 位浮点数表示的复数</td>
</tr>
</tbody>
</table>
<hr />
<h1>12.5 NumPy 常用函数总览</h1>
<table>
<thead>
<tr>
<th>分类</th>
<th>函数</th>
</tr>
</thead>
<tbody>
<tr>
<td>基本数学</td>
<td><code>np.sqrt(x)</code>、<code>np.exp(x)</code>、<code>np.log(x)</code>、<code>np.sin(x)</code>、<code>np.abs(x)</code>、<code>np.power(a, b)</code>、<code>np.round(x, n)</code></td>
</tr>
<tr>
<td>统计函数</td>
<td><code>np.sum(x)</code>、<code>np.mean(x)</code>、<code>np.median(x)</code>、<code>np.std(x)</code>、<code>np.var(x)</code>、<code>np.min(x)</code> / <code>np.max(x)</code>、<code>np.percentile(x, q)</code></td>
</tr>
<tr>
<td>比较函数</td>
<td><code>np.greater(a, b)</code>、<code>np.less(a, b)</code>、<code>np.equal(a, b)</code>、<code>np.logical_and(a, b)</code>、<code>np.where(condition, x, y)</code></td>
</tr>
<tr>
<td>去重函数</td>
<td><code>np.unique(x)</code>、<code>np.in1d(a, b)</code></td>
</tr>
<tr>
<td>排序函数</td>
<td><code>np.sort(x)</code>、<code>x.sort()</code>、<code>np.argsort(x)</code>、<code>np.lexsort(keys)</code></td>
</tr>
<tr>
<td>数组操作</td>
<td><code>np.concatenate((a, b))</code>、<code>np.split(x, indices)</code>、<code>np.reshape(x, shape)</code>、<code>np.copy(x)</code>、<code>np.isnan(x)</code></td>
</tr>
</tbody>
</table>
<hr />
<h1>12.6 函数补充</h1>
<p>下表列出了前文未详细展开、但在实际数据分析中常用的补充函数：</p>
<table>
<thead>
<tr>
<th>函数</th>
<th>功能</th>
<th>示例</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>np.concatenate((a, b))</code></td>
<td>将多个数组沿指定轴拼接</td>
<td><code>np.concatenate(([1, 2], [3, 4]))</code> → <code>[1 2 3 4]</code></td>
</tr>
<tr>
<td><code>np.count_nonzero(x)</code></td>
<td>统计非零元素（或满足条件）的个数</td>
<td><code>np.count_nonzero(temps &gt; 30)</code> 统计超过30的天数</td>
</tr>
<tr>
<td><code>axis</code> 参数</td>
<td>指定聚合方向：<code>axis=0</code> 按列、<code>axis=1</code> 按行</td>
<td><code>np.sum(arr, axis=1)</code> 计算每行之和</td>
</tr>
</tbody>
</table>
<hr />
<h1>12.7 本节小结</h1>
<p>本节完成了以下内容：</p>
<ul>
<li>回顾了 NumPy 的核心定位与功能：<code>ndarray</code> 多维数组、向量化运算、磁盘读写、线性代数与随机数等。</li>
<li>梳理了 ndarray 的三大特性（多维性、同质性、高效性）与八项常用属性。</li>
<li>归纳了 ndarray 的六大创建分类及对应方法。</li>
<li>整理了 ndarray 支持的全部数据类型（布尔、整数、浮点数、复数）。</li>
<li>汇总了 NumPy 常用函数速查表，覆盖基本数学、统计、比较、去重、排序和数组操作六大类。</li>
<li>补充了 <code>np.concatenate</code>、<code>np.count_nonzero</code>、<code>axis</code> 参数等实用函数。</li>
</ul>
<p>下一节我们将学习 <strong>Pandas</strong> 库。</p>
<hr />
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="信息学"/>
  </entry>
  <entry>
    <title>数据分析｜ndarray的特性与属性</title>
    <link href="https://luq-blog.xyz/posts/2026-07-01-data-notes-03-ndarray-features/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-07-01-data-notes-03-ndarray-features/</id>
    <published>2026-07-01T00:00:00.000Z</published>
    <updated>2026-07-01T00:00:00.000Z</updated>
    <summary>本节介绍 NumPy 数组（ndarray）的核心特性与常用属性，帮助读者深入理解 ndarray 的多维性、同质性以及各项属性的含义与用法。</summary>
    <content type="html"><![CDATA[<h1>3.1 ndarray 的核心特性</h1>
<p>NumPy 数组（ndarray）是 NumPy 库的核心数据结构，它是一种高效的多维同构数组。ndarray 具有以下三个核心特性：</p>
<ul>
<li><strong>多维性</strong>：支持 0 维（标量）、1 维（向量）、2 维（矩阵）及更高维数组</li>
<li><strong>同质性</strong>：所有元素类型必须一致（通过 <code>dtype</code> 指定）</li>
<li><strong>高效性</strong>：基于连续内存块存储，支持向量化运算</li>
</ul>
<h2>3.1.1 多维性</h2>
<p>ndarray 可以表示从 0 维到高维的多种数据结构。通过 <code>ndim</code> 属性可以查看数组的维度数量（number of dimensions）。</p>
<p><strong>0 维数组（标量）：</strong></p>
<pre><code>import numpy as np

arr = np.array(5)  # 创建0维ndarray数组
print(arr)
print('arr的维度是:', arr.ndim)
</code></pre>
<p>输出：</p>
<pre><code>5
arr的维度是: 0
</code></pre>
<p><strong>1 维数组（向量）：</strong></p>
<pre><code>arr = np.array([1, 2, 3])  # 创建1维ndarray数组
print(arr)
print('arr的维度是:', arr.ndim)
</code></pre>
<p>输出：</p>
<pre><code>[1 2 3]
arr的维度是: 1
</code></pre>
<p><strong>2 维数组（矩阵）：</strong></p>
<pre><code>arr = np.array([[1, 2, 3], [4, 5, 6]])  # 创建2维ndarray数组
print(arr)
print('arr的维度是:', arr.ndim)
</code></pre>
<p>输出：</p>
<pre><code>[[1 2 3]
 [4 5 6]]
arr的维度是: 2
</code></pre>
<h2>3.1.2 同质性</h2>
<p>ndarray 要求所有元素类型必须一致。当传入不同类型的元素时，NumPy 会自动进行类型转换，将所有元素统一为同一种类型。</p>
<p><strong>整型与字符串混合：</strong></p>
<pre><code>arr = np.array([1, 'hello'])  # 不同类型的元素会被强制转换为同一类型
print(arr)
</code></pre>
<p>输出：</p>
<pre><code>['1' 'hello']
</code></pre>
<p>整型 <code>1</code> 被自动转换为字符串 <code>'1'</code>，数组整体变为字符串类型。</p>
<p><strong>整型与浮点型混合：</strong></p>
<pre><code>arr = np.array([1, 2.5])  # 不同类型的元素会被强制转换为同一类型
print(arr)
</code></pre>
<p>输出：</p>
<pre><code>[1.  2.5]
</code></pre>
<p>整型 <code>1</code> 被自动转换为浮点型 <code>1.0</code>，数组整体变为浮点类型。</p>
<hr />
<h1>3.2 ndarray 的常用属性</h1>
<p>ndarray 提供了丰富的属性来描述数组的特征。以下是最常用的属性及其说明：</p>
<table>
<thead>
<tr>
<th>属性名称</th>
<th>通俗解释</th>
<th>使用示例</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>shape</code></td>
<td>数组的形状：行数和列数（或更高维度的尺寸）</td>
<td><code>arr.shape</code></td>
</tr>
<tr>
<td><code>ndim</code></td>
<td>维度数量：数组是几维的（1维、2维、3维等）</td>
<td><code>arr.ndim</code></td>
</tr>
<tr>
<td><code>size</code></td>
<td>总元素个数：数组中所有元素的总数</td>
<td><code>arr.size</code></td>
</tr>
<tr>
<td><code>dtype</code></td>
<td>元素类型：数组中元素的类型（整数、浮点数等）</td>
<td><code>arr.dtype</code></td>
</tr>
<tr>
<td><code>T</code></td>
<td>转置：行变列，列变行</td>
<td><code>arr.T</code></td>
</tr>
<tr>
<td><code>itemsize</code></td>
<td>单个元素占用的内存字节数</td>
<td><code>arr.itemsize</code></td>
</tr>
<tr>
<td><code>nbytes</code></td>
<td>数组总内存占用量：<code>size * itemsize</code></td>
<td><code>arr.nbytes</code></td>
</tr>
<tr>
<td><code>flags</code></td>
<td>内存存储方式：是否连续存储（高级优化）</td>
<td><code>arr.flags</code></td>
</tr>
</tbody>
</table>
<p><strong>注意：</strong> 属性和方法的区分方式是看是否带括号——不带括号的是属性（如 <code>arr.ndim</code>），带括号的是方法。</p>
<p>下面通过不同维度的数组来演示这些属性的使用。</p>
<p><strong>0 维数组的属性：</strong></p>
<pre><code>arr = np.array(1)
print(arr)
print('数组的形状：', arr.shape)
print('数组的维度：', arr.ndim)
print('元素的个数：', arr.size)
print('数组的类型：', arr.dtype)
print('元素的转置：', arr.T)
</code></pre>
<p>输出：</p>
<pre><code>1
数组的形状： ()
数组的维度： 0
元素的个数： 1
数组的类型： int64
元素的转置： 1
</code></pre>
<p><strong>1 维数组的属性：</strong></p>
<pre><code>arr = np.array([1, 2.5, 3])
print(arr)
print('数组的形状：', arr.shape)
print('数组的维度：', arr.ndim)
print('元素的个数：', arr.size)
print('数组的类型：', arr.dtype)
print('元素的转置：', arr.T)
</code></pre>
<p>输出：</p>
<pre><code>[1.  2.5 3. ]
数组的形状： (3,)
数组的维度： 1
元素的个数： 3
数组的类型： float64
元素的转置： [1.  2.5 3. ]
</code></pre>
<p>注意 1 维数组的形状为 <code>(3,)</code>，表示只有一个维度，长度为 3。1 维数组的转置等于其自身。</p>
<p><strong>2 维数组的属性：</strong></p>
<pre><code>arr = np.array([[1, 2, 3], [4, 5, 6]])
print(arr)
print('数组的形状：', arr.shape)
print('数组的维度：', arr.ndim)
print('元素的个数：', arr.size)
print('数组的类型：', arr.dtype)
print('元素的转置：', arr.T)
</code></pre>
<p>输出：</p>
<pre><code>[[1 2 3]
 [4 5 6]]
数组的形状： (2, 3)
数组的维度： 2
元素的个数： 6
数组的类型： int64
元素的转置： [[1 4]
 [2 5]
 [3 6]]
</code></pre>
<p>2 维数组的转置操作会将行变为列、列变为行。原数组形状为 <code>(2, 3)</code>，转置后形状变为 <code>(3, 2)</code>。</p>
<hr />
<h1>3.3 矩阵的基本概念</h1>
<p>在深入学习 ndarray 之前，有必要了解一些矩阵的基本概念。矩阵是线性代数中的基础数据结构，也是 NumPy 中 2 维数组的数学原型。</p>
<h2>3.3.1 数组与矩阵的对比</h2>
<p>在 NumPy 中，不同维度的数组对应不同的数学对象：</p>
<table>
<thead>
<tr>
<th>名称</th>
<th>维度</th>
<th>示例</th>
<th>备注</th>
</tr>
</thead>
<tbody>
<tr>
<td>标量</td>
<td>0 维</td>
<td><code>5</code>, <code>3.14</code></td>
<td>单个数字，无行列</td>
</tr>
<tr>
<td>向量</td>
<td>1 维</td>
<td><code>[1, 2, 3]</code></td>
<td>只有行或列（一维数组）</td>
</tr>
<tr>
<td>矩阵</td>
<td>2 维</td>
<td><code>[[1, 2], [3, 4]]</code></td>
<td>严格的行列结构（二维表）</td>
</tr>
<tr>
<td>张量</td>
<td>≥3 维</td>
<td><code>[[[1, 2], [3, 4]]]</code></td>
<td>高阶数组（如 RGB 图像）</td>
</tr>
</tbody>
</table>
<h2>3.3.2 矩阵的定义</h2>
<p>矩阵是一个由行（row）和列（column）排列成的矩形数组。例如：</p>
<p>$$A=\begin{bmatrix} 1&amp;2&amp;3\ 4&amp;5&amp;6\end{bmatrix}$$</p>
<ul>
<li><strong>形状（shape）</strong>：这个矩阵有 2 行 3 列，记作 2×3 矩阵</li>
<li><strong>元素（entry）</strong>：矩阵中的每个数字称为元素</li>
</ul>
<h2>3.3.3 特殊矩阵</h2>
<p>在线性代数和 NumPy 中，有几类常见的特殊矩阵：</p>
<table>
<thead>
<tr>
<th>矩阵类型</th>
<th>定义</th>
<th>例子</th>
</tr>
</thead>
<tbody>
<tr>
<td>零矩阵</td>
<td>所有元素为 0</td>
<td>$\begin{bmatrix} 0&amp;0\ 0&amp;0\end{bmatrix}$</td>
</tr>
<tr>
<td>单位矩阵</td>
<td>对角线上为 1，其余为 0</td>
<td>$\begin{bmatrix} 1&amp;0\ 0&amp;1\end{bmatrix}$</td>
</tr>
<tr>
<td>对角矩阵</td>
<td>只有对角线有非零值</td>
<td>$\begin{bmatrix} 2&amp;0\ 0&amp;3\end{bmatrix}$</td>
</tr>
<tr>
<td>对称矩阵</td>
<td>$A=A^{T}$</td>
<td>$\begin{bmatrix} 1&amp;2\ 2&amp;3\end{bmatrix}$</td>
</tr>
</tbody>
</table>
<p>这些特殊矩阵在 NumPy 中都有对应的创建方法，我们将在下一节中详细学习。</p>
<hr />
<h1>3.4 本节小结</h1>
<p>本节完成了以下内容：</p>
<ul>
<li>介绍了 ndarray 的核心特性：多维性、同质性和高效性</li>
<li>讲解了 ndarray 的常用属性：<code>shape</code>、<code>ndim</code>、<code>size</code>、<code>dtype</code>、<code>T</code>、<code>itemsize</code>、<code>nbytes</code>、<code>flags</code></li>
<li>了解了矩阵的基本概念，包括标量、向量、矩阵、张量的区别，以及特殊矩阵的类型</li>
</ul>
<p>下一节我们将学习 ndarray 的各种创建方法。</p>
<hr />
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="信息学"/>
  </entry>
  <entry>
    <title>数据分析｜ndarray的创建</title>
    <link href="https://luq-blog.xyz/posts/2026-07-01-data-notes-04-ndarray-creation/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-07-01-data-notes-04-ndarray-creation/</id>
    <published>2026-07-01T00:00:00.000Z</published>
    <updated>2026-07-01T00:00:00.000Z</updated>
    <summary>本节系统介绍 NumPy 数组（ndarray）的多种创建方法，包括基础构造、预定义形状填充、数值范围生成、特殊矩阵生成和随机数组生成。</summary>
    <content type="html"><![CDATA[<p>NumPy 提供了丰富的方法来创建 ndarray，根据使用场景的不同，可以分为以下几大类：</p>
<table>
<thead>
<tr>
<th>用途</th>
<th>方法</th>
</tr>
</thead>
<tbody>
<tr>
<td>基础构造</td>
<td><code>np.array()</code>、<code>np.copy()</code></td>
</tr>
<tr>
<td>预定义形状填充</td>
<td><code>np.zeros()</code>、<code>np.ones()</code>、<code>np.empty()</code>、<code>np.full()</code></td>
</tr>
<tr>
<td>基于数值范围生成</td>
<td><code>np.arange()</code>、<code>np.linspace()</code>、<code>np.logspace()</code></td>
</tr>
<tr>
<td>特殊矩阵生成</td>
<td><code>np.eye()</code>、<code>np.diag()</code></td>
</tr>
<tr>
<td>随机数组生成</td>
<td><code>np.random.rand()</code>、<code>np.random.randn()</code>、<code>np.random.randint()</code></td>
</tr>
</tbody>
</table>
<p>下面逐一介绍这些创建方法。</p>
<hr />
<h1>4.1 基础构造</h1>
<h2>4.1.1 np.array()</h2>
<p><code>np.array()</code> 是最基础的创建方法，可以从 Python 列表、元组等序列对象创建 ndarray。</p>
<pre><code>import numpy as np

list1 = [4, 5, 6]
arr = np.array(list1, dtype=np.float64)  # 指定数据类型
print(arr.ndim)  # 属性和方法的区分：是否带括号，带括号的是方法，不带括号的是属性
print(arr)
</code></pre>
<p>输出：</p>
<pre><code>1
[4. 5. 6.]
</code></pre>
<p>通过 <code>dtype</code> 参数可以指定数组的元素类型。上述代码中，虽然列表元素是整数，但指定 <code>dtype=np.float64</code> 后，数组元素被转换为浮点型。</p>
<h2>4.1.2 np.copy()</h2>
<p><code>np.copy()</code> 用于创建一个与原数组内容相同但相互独立的新数组。修改新数组不会影响原数组。</p>
<pre><code>arr1 = np.copy(arr)  # 元素跟原始的数组相同，但是不是一个数组了
print(arr1)
arr1[0] = 8
print(arr1)
print(arr)  # 原始数组不变
</code></pre>
<p>输出：</p>
<pre><code>[4. 5. 6.]
[8. 5. 6.]
[4. 5. 6.]
</code></pre>
<p><code>np.copy()</code> 执行的是<strong>深拷贝</strong>，新数组与原数组在内存中完全独立。这与直接赋值（<code>arr1 = arr</code>）不同，直接赋值只是创建了一个引用，修改其中一个会影响另一个。</p>
<hr />
<h1>4.2 预定义形状填充</h1>
<p>这类方法用于快速初始化固定形状的数组，常用于数据占位和预处理。</p>
<h2>4.2.1 np.zeros()</h2>
<p>创建一个全 0 数组。</p>
<pre><code>arr = np.zeros((2, 3), dtype=int)  # 创建一个2行3列的全0数组
print(arr)
print(arr.dtype)
</code></pre>
<p>输出：</p>
<pre><code>[[0 0 0]
 [0 0 0]]
int64
</code></pre>
<pre><code>arr = np.zeros((200,), dtype=int)  # 创建一个200个元素的全0数组
print(arr)
</code></pre>
<p>输出：</p>
<pre><code>[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0]
</code></pre>
<p><strong>注意：</strong> <code>shape</code> 参数需要传入一个元组。一维数组的形状为 <code>(200,)</code>，二维数组的形状为 <code>(2, 3)</code>。</p>
<h2>4.2.2 np.ones()</h2>
<p>创建一个全 1 数组。</p>
<pre><code>arr = np.ones((5, 8), dtype=int)  # 创建一个5行8列的全1数组
print(arr)
</code></pre>
<p>输出：</p>
<pre><code>[[1 1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1 1]]
</code></pre>
<h2>4.2.3 np.empty()</h2>
<p>创建一个未初始化的数组。由于不需要填充初始值，执行效率比 <code>np.zeros()</code> 更快。</p>
<pre><code>arr = np.empty((4, 3))  # 创建一个4行3列的未初始化数组，执行效率快
print(arr)
</code></pre>
<p>输出：</p>
<pre><code>[[1.31228284e-311 2.47032823e-322 0.00000000e+000]
 [0.00000000e+000 8.06630440e-308 2.92966904e-033]
 [4.71483083e-090 9.76836074e+165 1.55155023e+184]
 [9.95849747e-047 3.99910963e+252 2.32253274e-056]]
</code></pre>
<p>数组中的值是内存中的随机垃圾值，在使用前需要先赋值覆盖。适用于后续会完全填充数据的场景。</p>
<h2>4.2.4 np.full()</h2>
<p>创建一个指定形状并填充固定值的数组。</p>
<pre><code>arr = np.full((3, 4), 2026)  # 创建一个3行4列的全2026数组
print(arr)
</code></pre>
<p>输出：</p>
<pre><code>[[2026 2026 2026 2026]
 [2026 2026 2026 2026]
 [2026 2026 2026 2026]]
</code></pre>
<h2>4.2.5 基于已有形状的快捷方法</h2>
<p>NumPy 还提供了 <code>_like</code> 系列方法，可以创建与已有数组形状相同的新数组：</p>
<pre><code>arr1 = np.zeros_like(arr)    # 创建一个与arr形状相同的全0数组
print(arr1)
arr1 = np.empty_like(arr)    # 创建一个与arr形状相同的未初始化数组
print(arr1)
arr1 = np.ones_like(arr)     # 创建一个与arr形状相同的全1数组
print(arr1)
arr1 = np.full_like(arr, 2025)  # 创建一个与arr形状相同的全2025数组
print(arr1)
</code></pre>
<p>输出：</p>
<pre><code>[[0 0 0 0]
 [0 0 0 0]
 [0 0 0 0]]
[[      2656090036200                  50                   0
                    0]
 [  31244147616645120 4120349891991070059 3270516458839762277
  7090130557940611385]
 [7362600585106894132 3918748936486401889 8386112019185938739
  7233687420873831285]]
[[1 1 1 1]
 [1 1 1 1]
 [1 1 1 1]]
[[2025 2025 2025 2025]
 [2025 2025 2025 2025]
 [2025 2025 2025 2025]]
</code></pre>
<p><code>_like</code> 系列方法在需要创建与某个数组形状一致的占位数组时非常方便，无需手动指定 <code>shape</code>。</p>
<hr />
<h1>4.3 基于数值范围生成</h1>
<p>这类方法用于生成数值序列，常用于模拟时间序列、坐标网格等。</p>
<h2>4.3.1 np.arange()</h2>
<p>生成等差数列，用法类似 Python 内置的 <code>range()</code>，但返回的是 ndarray。</p>
<pre><code>arr = np.arange(2, 12, 2)  # start, stop(不取), step
print(arr)
</code></pre>
<p>输出：</p>
<pre><code>[ 2  4  6  8 10]
</code></pre>
<p>参数说明：<code>start</code> 为起始值，<code>stop</code> 为终止值（不包含），<code>step</code> 为步长。</p>
<h2>4.3.2 np.linspace()</h2>
<p>生成等间隔数列，与 <code>np.arange()</code> 的区别在于：<code>np.linspace()</code> 的第三个参数是元素个数（而非步长），且<strong>默认包含终止值</strong>。</p>
<pre><code>arr = np.linspace(0, 100, 5)  # start, stop(取), num
print(arr)
</code></pre>
<p>输出：</p>
<pre><code>[  0.  25.  50.  75. 100.]
</code></pre>
<p>在 0 到 100 之间均匀生成 5 个元素，间隔为 25。</p>
<p><strong>指定数据类型：</strong></p>
<pre><code>arr = np.linspace(0, 100, 5, dtype=int)
print(arr)
arr = np.arange(0, 101, 25, dtype=int)
print(arr)
</code></pre>
<p>输出：</p>
<pre><code>[  0  25  50  75 100]
[  0  25  50  75 100]
</code></pre>
<p>通过指定 <code>dtype=int</code>，可以让 <code>np.linspace()</code> 和 <code>np.arange()</code> 产生相同的结果。</p>
<h2>4.3.3 np.logspace()</h2>
<p>生成对数间隔数列。先将指数等间隔分布，然后以指定底数取幂。</p>
<pre><code>arr = np.logspace(0, 4, 3, base=2, dtype=int)  # 先把指数等间隔分布在0-4之间，然后以2为底取幂
print(arr)
</code></pre>
<p>输出：</p>
<pre><code>[ 1  4 16]
</code></pre>
<p>解释：指数在 0 到 4 之间等间隔取 3 个值，即 0、2、4。然后计算 $2^0=1$、$2^2=4$、$2^4=16$。</p>
<hr />
<h1>4.4 特殊矩阵生成</h1>
<h2>4.4.1 np.eye()</h2>
<p>创建单位矩阵（主对角线上的数字为 1，其余为 0）。</p>
<pre><code>arr = np.eye(3, 4, dtype=int)
print(arr)
</code></pre>
<p>输出：</p>
<pre><code>[[1 0 0 0]
 [0 1 0 0]
 [0 0 1 0]]
</code></pre>
<p>参数说明：第一个参数为行数，第二个参数为列数。当行数和列数相等时，生成的是标准单位矩阵；不等时，生成的是广义的单位矩阵。</p>
<h2>4.4.2 np.diag()</h2>
<p>创建对角矩阵（主对角线上是非零数字，其余为 0）。</p>
<pre><code>arr = np.diag([1, 2, 3])  # 创建一个对角矩阵
print(arr)
</code></pre>
<p>输出：</p>
<pre><code>[[1 0 0]
 [0 2 0]
 [0 0 3]]
</code></pre>
<p>传入一个一维数组，<code>np.diag()</code> 会将其元素放置在新数组的主对角线上。</p>
<hr />
<h1>4.5 随机数组生成</h1>
<p>随机数组生成常用于模拟实验数据、初始化神经网络权重等场景。NumPy 的 <code>random</code> 模块提供了多种随机数生成方法。</p>
<h2>4.5.1 np.random.rand()</h2>
<p>生成 0 到 1 之间的随机浮点数，服从均匀分布。</p>
<pre><code>arr = np.random.rand(2, 3)
print(arr)
</code></pre>
<p>输出（结果随机）：</p>
<pre><code>[[0.4974305  0.00461739 0.7830449 ]
 [0.35404588 0.65308817 0.99276538]]
</code></pre>
<h2>4.5.2 np.random.uniform()</h2>
<p>生成指定范围区间的随机浮点数，服从均匀分布。</p>
<pre><code>arr = np.random.uniform(3, 6, (2, 3))  # 生成2行3列的随机浮点数，范围在3到6之间
print(arr)
</code></pre>
<p>输出（结果随机）：</p>
<pre><code>[[3.71807012 3.88918494 4.92140544]
 [5.65113597 5.43057318 5.76969752]]
</code></pre>
<p>参数说明：第一个参数为下界，第二个参数为上界，第三个参数为数组形状。</p>
<h2>4.5.3 np.random.randint()</h2>
<p>生成指定范围区间的随机整数。</p>
<pre><code>arr = np.random.randint(3, 30, (2, 3))  # 生成2行3列的随机整数，范围在3到30之间
print(arr)
</code></pre>
<p>输出（结果随机）：</p>
<pre><code>[[ 5  9  6]
 [24 23  6]]
</code></pre>
<p>参数说明：第一个参数为下界（包含），第二个参数为上界（不包含），第三个参数为数组形状。</p>
<h2>4.5.4 np.random.randn()</h2>
<p>生成服从标准正态分布（均值为 0，标准差为 1）的随机数列，数值基本在 -3 到 3 之间，两边概率小，中间概率大。</p>
<pre><code>arr = np.random.randn(2, 3)  # 生成2行3列的随机数，服从标准正态分布
print(arr)
</code></pre>
<p>输出（结果随机）：</p>
<pre><code>[[-0.92029395  0.6566202   0.00439262]
 [ 1.92088964  1.7643093   0.92341786]]
</code></pre>
<h2>4.5.5 设置随机种子</h2>
<p>使用 <code>np.random.seed()</code> 可以设置随机种子，使得每次生成的随机数序列相同，便于实验复现。</p>
<pre><code>np.random.seed(20)
arr = np.random.randint(1, 10, (2, 5))
print(arr)
</code></pre>
<p>输出：</p>
<pre><code>[[4 5 7 8 3]
 [1 7 9 6 4]]
</code></pre>
<p>设置相同的随机种子后，每次运行生成的随机数序列完全一致。这在需要复现实验结果时非常有用。</p>
<hr />
<h1>4.6 本节小结</h1>
<p>本节完成了以下内容：</p>
<ul>
<li>介绍了 <code>np.array()</code> 和 <code>np.copy()</code> 的基础构造方法</li>
<li>讲解了预定义形状填充方法：<code>np.zeros()</code>、<code>np.ones()</code>、<code>np.empty()</code>、<code>np.full()</code> 及其 <code>_like</code> 系列</li>
<li>学习了基于数值范围生成的方法：<code>np.arange()</code>、<code>np.linspace()</code>、<code>np.logspace()</code></li>
<li>了解了特殊矩阵的生成方法：<code>np.eye()</code>、<code>np.diag()</code></li>
<li>掌握了随机数组的生成方法：<code>np.random.rand()</code>、<code>np.random.uniform()</code>、<code>np.random.randint()</code>、<code>np.random.randn()</code> 以及随机种子的设置</li>
</ul>
<p>下一节我们将学习 ndarray 的元素数据类型。</p>
<hr />
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="信息学"/>
  </entry>
  <entry>
    <title>数据分析｜ndarray的数据类型</title>
    <link href="https://luq-blog.xyz/posts/2026-07-01-data-notes-05-ndarray-dtype/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-07-01-data-notes-05-ndarray-dtype/</id>
    <published>2026-07-01T00:00:00.000Z</published>
    <updated>2026-07-01T00:00:00.000Z</updated>
    <summary>学习NumPy中ndarray支持的数据类型，包括布尔、整数、浮点数和复数类型。</summary>
    <content type="html"><![CDATA[<h1>5.1 ndarray的数据类型</h1>
<p>NumPy 数组（ndarray）的值之所以高效，离不开底层数据类型的支撑。ndarray 要求所有元素必须是<strong>同一数据类型</strong>，这正是它能在连续内存上进行高速运算的基础。</p>
<h2>数据类型总览</h2>
<p>下表列出了 ndarray 支持的全部数据类型及其说明：</p>
<table>
<thead>
<tr>
<th>数据类型</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>bool</code></td>
<td>布尔类型（<code>True</code> / <code>False</code>）</td>
</tr>
<tr>
<td><code>int8</code>、<code>uint8</code></td>
<td>有符号、无符号的 8 位（1 字节）整型</td>
</tr>
<tr>
<td><code>int16</code>、<code>uint16</code></td>
<td>有符号、无符号的 16 位（2 字节）整型</td>
</tr>
<tr>
<td><code>int32</code>、<code>uint32</code></td>
<td>有符号、无符号的 32 位（4 字节）整型</td>
</tr>
<tr>
<td><code>int64</code>、<code>uint64</code></td>
<td>有符号、无符号的 64 位（8 字节）整型</td>
</tr>
<tr>
<td><code>float16</code></td>
<td>半精度浮点型</td>
</tr>
<tr>
<td><code>float32</code></td>
<td>单精度浮点型</td>
</tr>
<tr>
<td><code>float64</code></td>
<td>双精度浮点型</td>
</tr>
<tr>
<td><code>complex64</code></td>
<td>用两个 32 位浮点数表示的复数</td>
</tr>
<tr>
<td><code>complex128</code></td>
<td>用两个 64 位浮点数表示的复数</td>
</tr>
</tbody>
</table>
<p><strong>四大类型族：</strong></p>
<ul>
<li><strong>布尔类型</strong>：<code>bool</code></li>
<li><strong>整数类型</strong>：<code>int8</code> ~ <code>int64</code>（有符号）、<code>uint8</code> ~ <code>uint64</code>（无符号）</li>
<li><strong>浮点数类型</strong>：<code>float16</code>、<code>float32</code>、<code>float64</code></li>
<li><strong>复数类型</strong>：<code>complex64</code>、<code>complex128</code></li>
</ul>
<h2>指定数据类型创建数组</h2>
<p>在创建数组时，可以通过 <code>dtype</code> 参数显式指定数据类型：</p>
<pre><code>import numpy as np

# 创建一个 int8 类型的 ndarray 数组
arr = np.array([1, 0, 2, 0, -1], dtype=np.int8)
print(arr)
print(arr.dtype)
</code></pre>
<p>输出：</p>
<pre><code>[ 1  0  2  0 -1]
int8
</code></pre>
<p><strong>要点说明：</strong></p>
<ul>
<li><code>dtype=np.int8</code> 将数组元素限定为 8 位有符号整数，取值范围为 -128 ~ 127。</li>
<li>如果传入的值超出目标类型的范围，NumPy 会发生溢出而非报错，需注意数据安全。</li>
<li>不指定 <code>dtype</code> 时，NumPy 会根据输入数据自动推断类型（如整数默认 <code>int64</code>，浮点数默认 <code>float64</code>）。</li>
</ul>
<hr />
<h1>5.2 本节小结</h1>
<p>本节完成了以下内容：</p>
<ul>
<li>了解了 ndarray 支持的四大类型族：布尔、整数、浮点数、复数。</li>
<li>掌握了通过 <code>dtype</code> 参数在创建数组时显式指定数据类型。</li>
<li>理解了不同数据类型的字节占用与取值范围差异。</li>
</ul>
<p>下一节我们将学习 <strong>索引与切片</strong>，掌握如何从数组中精确提取所需数据。</p>
<hr />
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="信息学"/>
  </entry>
  <entry>
    <title>数据分析｜索引与切片</title>
    <link href="https://luq-blog.xyz/posts/2026-07-01-data-notes-06-indexing-slicing/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-07-01-data-notes-06-indexing-slicing/</id>
    <published>2026-07-01T00:00:00.000Z</published>
    <updated>2026-07-01T00:00:00.000Z</updated>
    <summary>学习NumPy中ndarray的索引与切片操作，包括基本索引、连续切片、布尔索引及二维数组的访问方式。</summary>
    <content type="html"><![CDATA[<h1>6.1 索引与切片概述</h1>
<p>索引与切片是从数组中提取目标数据的核心手段。NumPy 的索引机制比 Python 原生列表更加强大，不仅支持常规的位置索引和切片，还支持布尔条件筛选等高级用法。</p>
<p>下表列出了常用的索引/切片类型及其用法：</p>
<table>
<thead>
<tr>
<th>类型</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>基本索引</td>
<td>通过整数索引直接访问元素，索引从 0 开始</td>
</tr>
<tr>
<td>行/列切片</td>
<td>使用冒号 <code>:</code> 切片语法选择行或列的子集</td>
</tr>
<tr>
<td>连续切片</td>
<td>从起始索引到结束索引按步长切片</td>
</tr>
<tr>
<td>使用 <code>slice</code> 函数</td>
<td>通过 <code>slice(start, stop, step)</code> 定义切片规则</td>
</tr>
<tr>
<td>布尔索引</td>
<td>通过布尔条件筛选满足条件的元素，支持逻辑运算符 <code>&amp;</code>、<code>|</code></td>
</tr>
</tbody>
</table>
<hr />
<h1>6.2 一维数组的索引与切片</h1>
<p>一维数组的索引与切片规则和 Python 列表完全一致。</p>
<pre><code>import numpy as np

# 创建一维数组
arr = np.array([17, 63, 17, 8, 99, 7, 27, 14, 76, 59, 26, 4, 75, 76, 62, 78, 84, 58, 95, 33])
print(arr)
</code></pre>
<p>输出：</p>
<pre><code>[17 63 17  8 99  7 27 14 76 59 26  4 75 76 62 78 84 58 95 33]
</code></pre>
<p><strong>基本索引与各类切片：</strong></p>
<pre><code>print(arr[0])              # 索引第一个元素
print(arr[:])              # 获取全部数据
print(arr[2:5])            # start:end+1，左闭右开区间
print(arr[slice(2, 15, 3)])  # start, end+1, step
print(arr[(arr &gt; 10) &amp; (arr &lt; 70)])  # 布尔索引
</code></pre>
<p>输出：</p>
<pre><code>17
[17 63 17  8 99  7 27 14 76 59 26  4 75 76 62 78 84 58 95 33]
[17  8 99]
[17  7 76  4 62]
[17 63 17 27 14 59 26 62 58 33]
</code></pre>
<p><strong>要点说明：</strong></p>
<ul>
<li><code>arr[0]</code>：通过整数下标直接访问单个元素。</li>
<li><code>arr[:]</code>：不传参数表示获取全部元素。</li>
<li><code>arr[2:5]</code>：取索引 2 到 4 的元素（左闭右开）。</li>
<li><code>arr[slice(2, 15, 3)]</code>：等价于 <code>arr[2:15:3]</code>，从索引 2 开始每隔 3 个取一个，到索引 14 为止。</li>
<li><code>arr[(arr &gt; 10) &amp; (arr &lt; 70)]</code>：布尔索引，筛选出同时满足大于 10 且小于 70 的所有元素。注意使用 <code>&amp;</code> 而非 <code>and</code>。</li>
</ul>
<hr />
<h1>6.3 二维数组的索引与切片</h1>
<p>二维数组的索引需要同时指定<strong>行索引</strong>和<strong>列索引</strong>，用逗号分隔。</p>
<pre><code># 创建二维数组（4行8列）
arr = np.random.randint(1, 100, (4, 8))
print(arr)
</code></pre>
<p>输出：</p>
<pre><code>[[11  7 76 19  4 78 18 44]
 [17 19 80 91 60 72 76 30]
 [46  7 64 79 25 19 94 34]
 [79 43 95  4 19 71 53 23]]
</code></pre>
<p><strong>各种索引与切片操作：</strong></p>
<pre><code>print(arr[1, 3])              # 索引第2行第4列的元素
print(arr[1, :])              # 获取第2行的所有数据
print(arr[1, 2:5])            # 获取第2行的第3到第5个元素
print(arr[arr &gt; 50])          # 获取所有大于50的元素
print(arr[2][arr[2] &gt; 50])    # 获取第3行中大于50的元素
print(arr[:, 3])              # 获取第4列的所有元素
</code></pre>
<p>输出：</p>
<pre><code>91
[17 19 80 91 60 72 76 30]
[80 91 60]
[76 78 80 91 60 72 76 64 79 94 79 95 71 53]
[64 79 94]
[19 91 79  4]
</code></pre>
<p><strong>要点说明：</strong></p>
<ul>
<li><code>arr[1, 3]</code>：先行后列，取第 2 行第 4 列的单个元素。</li>
<li><code>arr[1, :]</code>：取第 2 行的全部列。冒号 <code>:</code> 表示"全部"。</li>
<li><code>arr[1, 2:5]</code>：取第 2 行中索引 2 到 4 的元素。</li>
<li><code>arr[arr &gt; 50]</code>：对整个数组进行布尔筛选，返回一维数组（所有满足条件的元素展平排列）。</li>
<li><code>arr[2][arr[2] &gt; 50]</code>：先取第 3 行，再在该行内筛选大于 50 的元素。</li>
<li><code>arr[:, 3]</code>：冒号表示取所有行，<code>3</code> 表示第 4 列。</li>
</ul>
<hr />
<h1>6.4 本节小结</h1>
<p>本节完成了以下内容：</p>
<ul>
<li>掌握了一维数组的基本索引、切片、<code>slice</code> 函数和布尔索引。</li>
<li>掌握了二维数组的行列索引、行列切片和条件筛选。</li>
<li>理解了布尔索引中使用 <code>&amp;</code>（与）、<code>|</code>（或）进行组合条件筛选。</li>
</ul>
<p>下一节我们将学习 <strong>ndarray 的运算</strong>，包括算术运算、广播机制和矩阵乘法。</p>
<hr />
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="信息学"/>
  </entry>
  <entry>
    <title>数据分析｜ndarray的运算</title>
    <link href="https://luq-blog.xyz/posts/2026-07-01-data-notes-07-ndarray-operations/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-07-01-data-notes-07-ndarray-operations/</id>
    <published>2026-07-01T00:00:00.000Z</published>
    <updated>2026-07-01T00:00:00.000Z</updated>
    <summary>学习NumPy中ndarray的算术运算、广播机制与矩阵乘法，理解数组运算与列表运算的本质区别。</summary>
    <content type="html"><![CDATA[<h1>7.1 数组间的算术运算</h1>
<p>ndarray 最强大的特性之一是支持<strong>逐元素的向量化运算</strong>——无需手动编写循环，直接对数组使用 <code>+</code>、<code>-</code>、<code>*</code>、<code>/</code>、<code>**</code> 等运算符即可完成对应元素的计算。</p>
<h2>一维数组运算</h2>
<pre><code>import numpy as np

a = np.array([1, 2, 3])
b = np.array([4, 5, 6])

print(a + b)   # 对应元素相加
print(a - b)   # 对应元素相减
print(a * b)   # 对应元素相乘
print(a / b)   # 对应元素相除
print(a ** 2)  # 幂运算
</code></pre>
<p>输出：</p>
<pre><code>[5 7 9]
[-3 -3 -3]
[ 4 10 18]
[0.25 0.4  0.5 ]
[1 4 9]
</code></pre>
<h2>与列表的对比</h2>
<p>Python 原生列表的 <code>+</code> 运算是<strong>拼接</strong>而非逐元素相加：</p>
<pre><code>c = [1, 2, 3]
d = [4, 5, 6]
print(c + d)  # 列表直接相加是拼接，而不是对应元素相加
</code></pre>
<p>输出：</p>
<pre><code>[1, 2, 3, 4, 5, 6]
</code></pre>
<p>如果要对列表逐元素相加，需要用循环：</p>
<pre><code>for i in range(len(c)):
    d[i] = c[i] + d[i]
print(d)  # 列表对应元素相加
</code></pre>
<p>输出：</p>
<pre><code>[5, 7, 9]
</code></pre>
<p><strong>对比结论：</strong> ndarray 的向量化运算不仅代码更简洁，执行效率也远高于手动循环。</p>
<h2>二维数组运算</h2>
<pre><code>a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
b = np.array([[4, 5, 6], [7, 8, 9], [1, 2, 3]])

print(a + b)   # 对应元素相加
print(a - b)   # 对应元素相减
print(a * b)   # 对应元素相乘
print(a / b)   # 对应元素相除
print(a ** 2)  # 幂运算
</code></pre>
<p>输出：</p>
<pre><code>[[ 5  7  9]
 [11 13 15]
 [ 8 10 12]]
[[-3 -3 -3]
 [-3 -3 -3]
 [ 6  6  6]]
[[ 4 10 18]
 [28 40 54]
 [ 7 16 27]]
[[0.25       0.4        0.5       ]
 [0.57142857 0.625      0.66666667]
 [7.         4.         3.        ]]
[[ 1  4  9]
 [16 25 36]
 [49 64 81]]
</code></pre>
<p>二维数组的算术运算同样是逐元素进行的，要求两个数组<strong>形状相同</strong>。</p>
<hr />
<h1>7.2 广播机制</h1>
<p>当两个数组形状不完全相同时，NumPy 会尝试通过**广播机制（Broadcasting）**自动扩展形状，使运算得以进行。</p>
<p><strong>广播规则：</strong> 从末尾维度开始逐维度比较，两个维度满足以下条件之一即可广播：</p>
<ol>
<li>维度大小相同；</li>
<li>其中一个维度为 1。</li>
</ol>
<pre><code>a = np.array([1, 2, 3])       # 形状 (3,)
b = np.array([[4], [5], [6]])  # 形状 (3, 1)

print(a + b)  # 广播机制
print(b - a)
</code></pre>
<p>输出：</p>
<pre><code>[[5 6 7]
 [6 7 8]
 [7 8 9]]
[[3 2 1]
 [4 3 2]
 [5 4 3]]
</code></pre>
<p><strong>广播过程图解：</strong></p>
<pre><code>a (1×3)  扩展为：     b (3×1)  扩展为：
1 2 3                 4 4 4
1 2 3                 5 5 5
1 2 3                 6 6 6
</code></pre>
<p><strong>不能广播的情况：</strong></p>
<pre><code># a = np.array([1, 2, 3])  # 形状 (3,)
# b = np.array([4, 5])      # 形状 (2,)
# print(a + b)  # 报错：形状不兼容，无法广播
</code></pre>
<hr />
<h1>7.3 矩阵乘法</h1>
<p>除了逐元素运算，NumPy 还支持线性代数中的<strong>矩阵乘法</strong>，使用 <code>@</code> 运算符或 <code>np.dot()</code> 方法。</p>
<pre><code>a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
b = np.array([[4, 5, 6], [7, 8, 9], [1, 2, 3]])

print(a @ b)  # 矩阵乘法
</code></pre>
<p>输出：</p>
<pre><code>[[ 21  27  33]
 [ 57  72  87]
 [ 93 117 141]]
</code></pre>
<p><strong>矩阵乘法计算过程：</strong></p>
<pre><code>a                    b
1 2 3                4 5 6
4 5 6                7 8 9
7 8 9                1 2 3
</code></pre>
<p>结果矩阵第 (i, j) 位置 = a 的第 i 行 与 b 的第 j 列 对应元素相乘再求和。</p>
<p>以结果 (0, 0) 位置为例：</p>
<pre><code>a 第1行: 1 2 3
b 第1列: 4 7 1
1×4 + 2×7 + 3×1 = 4 + 14 + 3 = 21
</code></pre>
<hr />
<h1>7.4 本节小结</h1>
<p>本节完成了以下内容：</p>
<ul>
<li>掌握了 ndarray 的逐元素算术运算（<code>+</code>、<code>-</code>、<code>*</code>、<code>/</code>、<code>**</code>），理解了其与列表运算的本质区别。</li>
<li>理解了广播机制的规则：维度从末尾开始比较，相同或有一方为 1 时可广播。</li>
<li>掌握了矩阵乘法 <code>@</code> 运算符的使用方法及其计算原理。</li>
</ul>
<p>下一节我们将学习 NumPy 中的<strong>基本数学函数</strong>，包括平方根、指数、对数、三角函数等。</p>
<hr />
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="信息学"/>
  </entry>
  <entry>
    <title>数据分析｜基本数学函数</title>
    <link href="https://luq-blog.xyz/posts/2026-07-01-data-notes-08-math-functions/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-07-01-data-notes-08-math-functions/</id>
    <published>2026-07-01T00:00:00.000Z</published>
    <updated>2026-07-01T00:00:00.000Z</updated>
    <summary>学习NumPy中的基本数学函数，包括平方根、指数、对数、三角函数、绝对值、幂运算、取整和缺失值检测。</summary>
    <content type="html"><![CDATA[<h1>8.1 基本数学函数</h1>
<p>NumPy 提供了一系列数学函数，下表列出了常用的基本数学函数：</p>
<table>
<thead>
<tr>
<th>函数</th>
<th>功能</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>np.sqrt(x)</code></td>
<td>计算平方根</td>
</tr>
<tr>
<td><code>np.exp(x)</code></td>
<td>计算指数 $e^x$</td>
</tr>
<tr>
<td><code>np.log(x)</code></td>
<td>计算自然对数 $\ln x$</td>
</tr>
<tr>
<td><code>np.sin(x)</code> / <code>np.cos(x)</code></td>
<td>计算正弦值 / 余弦值</td>
</tr>
<tr>
<td><code>np.abs(x)</code></td>
<td>计算绝对值</td>
</tr>
<tr>
<td><code>np.power(a, b)</code></td>
<td>计算 $a^b$</td>
</tr>
<tr>
<td><code>np.round(x, n)</code></td>
<td>四舍五入到 n 位小数</td>
</tr>
<tr>
<td><code>np.ceil(x)</code> / <code>np.floor(x)</code></td>
<td>向上取整 / 向下取整</td>
</tr>
<tr>
<td><code>np.isnan(x)</code></td>
<td>检测是否为缺失值 NaN</td>
</tr>
</tbody>
</table>
<hr />
<h1>8.2 平方根</h1>
<p><code>np.sqrt()</code> 可以计算标量或数组中每个元素的平方根：</p>
<pre><code>import numpy as np

# 计算标量的平方根
print(np.sqrt(9))

# 计算列表中每个元素的平方根
print(np.sqrt([1, 4, 9]))

# 计算数组中每个元素的平方根
arr = np.array([1, 25, 81])
print(np.sqrt(arr))
</code></pre>
<p>输出：</p>
<pre><code>3.0
[1. 2. 3.]
[1. 5. 9.]
</code></pre>
<hr />
<h1>8.3 指数与对数</h1>
<p><code>np.exp(x)</code> 计算 $e^x$，<code>np.log(x)</code> 计算 $\ln x$（自然对数），两者互为逆运算。</p>
<pre><code># 计算 e 的 1 次方
print(np.exp(1))
</code></pre>
<p>输出：</p>
<pre><code>2.718281828459045
</code></pre>
<pre><code># 计算 e 的自然对数
print(np.log(2.718281828459045))
</code></pre>
<p>输出：</p>
<pre><code>1.0
</code></pre>
<hr />
<h1>8.4 三角函数</h1>
<p><code>np.sin()</code> 和 <code>np.cos()</code> 接受弧度值作为参数。NumPy 提供了 <code>np.pi</code> 常量表示 $\pi$。</p>
<pre><code># 计算正弦值和余弦值
print(np.sin(np.pi / 2))  # sin(90°) = 1
print(np.cos(np.pi))       # cos(180°) = -1
</code></pre>
<p>输出：</p>
<pre><code>1.0
-1.0
</code></pre>
<hr />
<h1>8.5 绝对值</h1>
<p><code>np.abs()</code> 计算数组中每个元素的绝对值：</p>
<pre><code>arr = np.array([-1, 1, 2, -3])
print(np.abs(arr))
</code></pre>
<p>输出：</p>
<pre><code>[1 1 2 3]
</code></pre>
<hr />
<h1>8.6 幂运算</h1>
<p><code>np.power(a, b)</code> 计算 a 的 b 次幂，支持数组逐元素运算：</p>
<pre><code>arr = np.array([-1, 1, 2, -3])
print(np.power(arr, 2))
</code></pre>
<p>输出：</p>
<pre><code>[1 1 4 9]
</code></pre>
<hr />
<h1>8.7 四舍五入与取整</h1>
<p><strong>四舍五入：</strong> <code>np.round()</code> 将数值四舍五入到指定小数位。注意：<code>.5</code> 的情况会向偶数方向舍入（银行家舍入法）。</p>
<pre><code>print(np.round([3.2, 4.51, 4.5, 8.1, 9.6]))
</code></pre>
<p>输出：</p>
<pre><code>[ 3.  5.  4.  8. 10.]
</code></pre>
<p><strong>向上取整与向下取整：</strong></p>
<pre><code>arr = np.array([1.6, 25.1, 81.7])
print(np.ceil(arr))   # 向上取整
print(np.floor(arr))  # 向下取整
</code></pre>
<p>输出：</p>
<pre><code>[ 2. 26. 82.]
[ 1. 25. 81.]
</code></pre>
<hr />
<h1>8.8 检测缺失值</h1>
<p><code>np.isnan()</code> 用于检测数组中是否存在 <code>NaN</code>（Not a Number）缺失值，返回布尔数组：</p>
<pre><code>result = np.isnan([1, 2, np.nan, 3])
print(result)
</code></pre>
<p>输出：</p>
<pre><code>[False False  True False]
</code></pre>
<p>在实际数据分析中，缺失值检测是数据清洗的重要前置步骤。<code>np.isnan()</code> 常与布尔索引配合使用，定位并处理缺失数据。</p>
<hr />
<h1>8.9 本节小结</h1>
<p>本节完成了以下内容：</p>
<ul>
<li>掌握了 <code>np.sqrt()</code>、<code>np.exp()</code>、<code>np.log()</code> 等指数/对数运算。</li>
<li>掌握了 <code>np.sin()</code>、<code>np.cos()</code> 等三角函数的使用（参数为弧度）。</li>
<li>掌握了 <code>np.abs()</code>、<code>np.power()</code>、<code>np.round()</code>、<code>np.ceil()</code>、<code>np.floor()</code> 等基础数学函数。</li>
<li>掌握了 <code>np.isnan()</code> 检测缺失值的方法。</li>
</ul>
<p>下一节我们将学习 NumPy 中的<strong>统计函数</strong>，包括求和、平均值、中位数、标准差、方差等。</p>
<hr />
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="信息学"/>
  </entry>
  <entry>
    <title>数据分析｜统计函数</title>
    <link href="https://luq-blog.xyz/posts/2026-07-01-data-notes-09-statistics/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-07-01-data-notes-09-statistics/</id>
    <published>2026-07-01T00:00:00.000Z</published>
    <updated>2026-07-01T00:00:00.000Z</updated>
    <summary>学习NumPy中的统计函数，包括求和、平均值、中位数、标准差、方差、最值、分位数、累积和与累积积。</summary>
    <content type="html"><![CDATA[<h1>9.1 统计函数概述</h1>
<p>下表列出了本节涉及的统计函数：</p>
<table>
<thead>
<tr>
<th>函数</th>
<th>功能</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>np.sum(x)</code></td>
<td>求和</td>
</tr>
<tr>
<td><code>np.mean(x)</code></td>
<td>平均值</td>
</tr>
<tr>
<td><code>np.median(x)</code></td>
<td>中位数</td>
</tr>
<tr>
<td><code>np.std(x)</code></td>
<td>标准差</td>
</tr>
<tr>
<td><code>np.var(x)</code></td>
<td>方差</td>
</tr>
<tr>
<td><code>np.min(x)</code> / <code>np.max(x)</code></td>
<td>最小值 / 最大值</td>
</tr>
<tr>
<td><code>np.argmin(x)</code> / <code>np.argmax(x)</code></td>
<td>最小值 / 最大值的索引</td>
</tr>
<tr>
<td><code>np.percentile(x, q)</code></td>
<td>计算第 q 百分位数</td>
</tr>
<tr>
<td><code>np.cumsum(x)</code></td>
<td>累积和</td>
</tr>
<tr>
<td><code>np.cumprod(x)</code></td>
<td>累积积</td>
</tr>
</tbody>
</table>
<hr />
<h1>9.2 求和</h1>
<p><code>np.sum()</code> 计算数组中所有元素的总和：</p>
<pre><code>import numpy as np

# 基本求和
print(np.sum([1, 2, 3]))

# 对数组求和
arr = np.array([15, 5, 12, 9, 7, 16, 19, 15])
print(np.sum(arr))
</code></pre>
<p>输出：</p>
<pre><code>6
98
</code></pre>
<hr />
<h1>9.3 平均值</h1>
<p><code>np.mean()</code> 计算数组元素的算术平均值：</p>
<pre><code>print(np.mean([1, 2, 3]))

arr = np.array([15, 5, 12, 9, 7, 16, 19, 15])
print(np.mean(arr))
</code></pre>
<p>输出：</p>
<pre><code>2.0
12.25
</code></pre>
<hr />
<h1>9.4 中位数</h1>
<p><code>np.median()</code> 计算数组元素的中位数（排序后位于中间位置的值）：</p>
<pre><code>print(np.median([1, 2, 4, 8]))

arr = np.array([15, 5, 12, 9, 7, 16, 19, 15])
print(np.median(arr))
</code></pre>
<p>输出：</p>
<pre><code>3.0
13.5
</code></pre>
<hr />
<h1>9.5 方差与标准差</h1>
<p>方差（<code>np.var()</code>）衡量数据的离散程度，标准差（<code>np.std()</code>）是方差的算术平方根。</p>
<pre><code>print(np.var([1, 2, 3]))    # 方差
print(np.std([1, 2, 3]))    # 标准差
</code></pre>
<p>输出：</p>
<pre><code>0.6666666666666666
0.816496580927726
</code></pre>
<p><strong>均值相同但方差不同的对比：</strong></p>
<pre><code>arr1 = np.array([1, 2, 1, 2, 1, 1, 1, 2])
arr2 = np.array([1, 0, 3, 0, 0, 0, 4, 3])

print(np.mean(arr1))   # 平均值相同
print(np.mean(arr2))
print(np.var(arr1))    # 方差不同
print(np.var(arr2))
</code></pre>
<p>输出：</p>
<pre><code>1.375
1.375
0.234375
2.484375
</code></pre>
<p>两个数组的平均值相同（1.375），但 <code>arr2</code> 的方差远大于 <code>arr1</code>，说明 <code>arr2</code> 的数据分布更加分散。</p>
<hr />
<h1>9.6 最大值与最小值</h1>
<p><code>np.max()</code> / <code>np.min()</code> 返回最值，<code>np.argmax()</code> / <code>np.argmin()</code> 返回最值的索引位置：</p>
<pre><code>arr = np.array([15, 5, 12, 9, 7, 16, 19, 15])
print(arr)
print(np.max(arr), np.argmax(arr))   # 最大值及其索引
print(np.min(arr), np.argmin(arr))   # 最小值及其索引
</code></pre>
<p>输出：</p>
<pre><code>[15  5 12  9  7 16 19 15]
19 6
5 1
</code></pre>
<ul>
<li>最大值为 19，位于索引 6。</li>
<li>最小值为 5，位于索引 1。</li>
</ul>
<hr />
<h1>9.7 分位数</h1>
<p><code>np.percentile(x, q)</code> 计算数组的第 q 百分位数，<code>np.median()</code> 本质上等价于 <code>np.percentile(x, 50)</code>。</p>
<pre><code>np.random.seed(0)
arr = np.random.randint(0, 100, 4)
print(arr)

print(np.median(arr))            # 中位数（第50百分位数）
print(np.percentile(arr, 25))    # 第25百分位数
</code></pre>
<p>输出：</p>
<pre><code>[44 47 64 67]
55.5
46.25
</code></pre>
<p><strong>第25百分位数的计算过程：</strong></p>
<pre><code>排序后: 44  47  64  67
位置 = 0.25 × 3 = 0.75
结果 = (47 - 44) × 0.75 + 44 = 46.25
</code></pre>
<hr />
<h1>9.8 累积和与累积积</h1>
<p><code>np.cumsum()</code> 计算累积和，<code>np.cumprod()</code> 计算累积积：</p>
<pre><code>arr = np.array([1, 2, 3])

print(np.sum(arr))       # 总和
print(np.cumsum(arr))    # 累积和
print(np.cumprod(arr))   # 累积积
</code></pre>
<p>输出：</p>
<pre><code>6
[1 3 6]
[1 2 6]
</code></pre>
<ul>
<li><strong>累积和</strong>：<code>[1, 1+2, 1+2+3]</code> = <code>[1, 3, 6]</code></li>
<li><strong>累积积</strong>：<code>[1, 1×2, 1×2×3]</code> = <code>[1, 2, 6]</code></li>
</ul>
<hr />
<h1>9.9 本节小结</h1>
<p>本节完成了以下内容：</p>
<ul>
<li>掌握了 <code>np.sum()</code>、<code>np.mean()</code>、<code>np.median()</code> 等集中趋势统计函数。</li>
<li>掌握了 <code>np.var()</code>、<code>np.std()</code> 等离散程度统计函数。</li>
<li>掌握了 <code>np.max()</code> / <code>np.min()</code> 及 <code>np.argmax()</code> / <code>np.argmin()</code> 最值查找。</li>
<li>掌握了 <code>np.percentile()</code> 分位数计算及其原理。</li>
<li>掌握了 <code>np.cumsum()</code> 和 <code>np.cumprod()</code> 累积运算。</li>
</ul>
<hr />
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="信息学"/>
  </entry>
  <entry>
    <title>记我拥有一台新电脑的经历</title>
    <link href="https://luq-blog.xyz/posts/2026-06-27-my-new-devices/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-06-27-my-new-devices/</id>
    <published>2026-06-27T00:00:00.000Z</published>
    <updated>2026-06-30T00:00:00.000Z</updated>
    <summary>意外收获小米17T与轻薄本ThinkBook 14，分享购机心路、设备配置，以及重装全套开发环境的开荒体验。</summary>
    <content type="html"><![CDATA[<h1>1. 前言</h1>
<p>这简直是我目前人生中最最最意料之外的事情！</p>
<hr />
<h1>2. 事情经过</h1>
<h2>2.1 物品“新手机” +1</h2>
<p>最近因为有一件对我来说就是很重要的事情，需要携带个人电脑出行的那种，所以今天下午我的父亲就带我去买一些必要的东西。</p>
<p>在此之前，我有自己的手机和笔记本电脑，不过电脑是游戏本，出行的话有些重，我一直想要一台新电脑，但是考虑到经济因素，我有些犹豫不决。另外我的手机不仅屏幕摔碎了，而且电池亏电严重（一天必须充两次电的那种地步），所以衡量再三，我最终决定还是换一部新手机就好了。</p>
<p>然后，我们就到电脑城附近去转转。那里有很多手机店，我们先后看了华为、荣耀、VIVO，不过我其实完全不需要那么高的性能去玩游戏，再考虑到价格，所以我最终选择了小米17T。</p>
<p>其实这已经对我来说很满足了。但是！</p>
<p>最令我意想不到的事情发生了。</p>
<h2>2.2 新电脑！这不是在做梦吧！？</h2>
<p>我父亲说，来都来了，不如到电脑城去看看吧。我当时以为“只是看看”而已，没想到......</p>
<p>我们一进去就先到门口的几家店看了看。他们都没标价，但是随口一问价格就是超过 5000 ...(*￣０￣)ノ。所以我想：哈哈，这么多钱，还是等我以后有钱了再买吧......</p>
<p>但就在这时候，父亲跟我说，我要是想要的话，也可以给我买。</p>
<p>你知道当我听到这句话的时候，经历了多么复杂的情绪么？！（限于在下贫乏的语言表达，这里实在无法详细描写）</p>
<p>所以啊，经过各种深思熟虑，我最后买了 ThinkBook 14。</p>
<hr />
<h1>3. 电脑配置</h1>
<p>2026年6月30日续。</p>
<p>到家后的心情实在难以平复，所以草草截图了事，供各位观看。</p>
<p><img src="1.webp" alt="电脑配置" /></p>
<hr />
<h1>4. 新电脑开荒</h1>
<p>个人认为，“买电脑”和“新电脑装机”的情绪体验是完全相反的两种极端。这是因为......</p>
<p>换新电脑，就意味着我得把 VS Code、 Anaconda、 Photoshop、 Git、 嘉立创EDA 全都重装一遍，Astro、Git 、PlatformIO 以及各种编程语言环境全部重新搭建一遍，重装插件、配置网络代理，还要解决之前踩过的的各类问题。</p>
<p>除了开发环境，还要安装各种常用软件，微信、钉钉、QQ、各类压缩与图片处理软件，甚至原来电脑里的文档、图片都要一一筛选并移动。</p>
<p>这真是，想想就让人头疼啊(￣﹃￣)</p>
<p>最后，经过我长达几个小时的装机能力复健（bushi），新电脑就真正属于我啦！</p>
<p>给你们看看成果（个人信息做了隐藏处理）：</p>
<blockquote>
<p>终于可以不用在桌面上放各种东西啦，之前的电脑由于是家庭使用，所以没这样做。</p>
<p><img src="2.webp" alt="桌面" /></p>
<p>由于是正版系统，可以直接用微软全家桶，不用装 WPS 了。</p>
<p><img src="3.webp" alt="微软全家桶" /></p>
<p>写代码试试。</p>
<p><img src="4.webp" alt="VSC" /></p>
</blockquote>
<hr />
<h1>5. 总结</h1>
<p>这次经历真的完全出乎我的意料，从原本只打算简单换个新手机，到意外收获一台心仪的 ThinkBook 14，算是近期最惊喜的一件事了。</p>
<p>新设备意味着新的开始，接下来就带着新装备，继续专注学业、深耕热爱，奔赴接下来的每一段旅程。</p>
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="思录"/>
  </entry>
  <entry>
    <title>记第一次申请护照的过程</title>
    <link href="https://luq-blog.xyz/posts/2026-06-26-my-first-passport/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-06-26-my-first-passport/</id>
    <published>2026-06-26T00:00:00.000Z</published>
    <updated>2026-06-29T00:00:00.000Z</updated>
    <summary>人生第一本护照办理记录。</summary>
    <content type="html"><![CDATA[<h1>1. 前言</h1>
<p>最近意外得到了一个出国的机会，期待了这么多天，终于成功入围了ヾ(≧▽≦*)o</p>
<p>所以周五这天下午我请假到市民中心申请护照。还怪激动的，哈哈（</p>
<h1>2. 申请过程</h1>
<h2>2.1 填写申请表</h2>
<p>首先毫无疑问，各种资料必须携带齐全（身份证、户口本以及其他要求的证明材料），找到对应窗口进行办理。</p>
<p>由于我之前没办过护照，所以一直不知道工作人员是什么样，但事实证明非常和蔼可亲，比想象中的好多啦≡[。。]≡。</p>
<p>而且，因为我是兵团户口，当时没人排队，所以整个过程很快。整体流程是<strong>填写申请表、现场采集证件照、录入双手指纹、电子签名、核对确认个人信息</strong>五步。喜出望外的是，整套流程居然只花了不到 2 小时，比我预期的好太多了。</p>
<h2>2.2 等待审核</h2>
<p>2026年6月29日续。</p>
<p>这段过程虽然很煎熬，不过呢，其实没有我想的那么长。</p>
<p>由于我当时时间比较紧迫，所以申请了加急办理（PS:加急办理无需额外付费），没想到居然在 1 个工作日——也就是申请之后的那个工作日——就拿到了人生中第一个护照！</p>
<p>这天我还在上学，晚上回去后就得知这一消息，感觉晚上又要激动得失眠了╥﹏╥...</p>
<h1>3. 总结</h1>
<p>总之，这一步进展非常顺利，接下来就是等待签证了......</p>
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="思录"/>
  </entry>
  <entry>
    <title>Git｜创建与切换分支</title>
    <link href="https://luq-blog.xyz/posts/2026-06-22-git-notes-03-branch/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-06-22-git-notes-03-branch/</id>
    <published>2026-06-22T00:00:00.000Z</published>
    <updated>2026-06-22T00:00:00.000Z</updated>
    <summary>理解 Git 分支的本质逻辑，通过完整示例掌握命令行与 VS Code 中的分支操作。</summary>
    <content type="html"><![CDATA[<h1>3.0 本节命令行汇总</h1>
<pre><code>git init                                   # 创建本地 Git 仓库
git add &lt;file&gt;                             # 暂存指定文件
git add .                                  # 暂存所有变更文件
git commit -m "message"                    # 提交变更
git branch                                 # 查看本地分支列表
git branch -v                              # 查看本地分支列表（详细）
git branch &lt;branch-name&gt;                   # 创建新分支
git checkout &lt;branch-name&gt;                 # 切换到目标分支
git checkout -b &lt;branch-name&gt;              # 创建新分支并立即切换
</code></pre>
<hr />
<h1>3.1 Git 分支的逻辑</h1>
<blockquote>
<p>注：本示意图来自B站 <code>@寰宇牛奶unimilk</code> 的视频。</p>
<p><img src="1.webp" alt="有向无环图示意图" /></p>
</blockquote>
<p>在开始动手操作之前，先理解 Git 分支的底层逻辑，有助于在合并、冲突处理等场景中保持清晰的思路。</p>
<p><strong>1. 分支的本质是指针。</strong></p>
<p>在 Git 中，分支不过是一个指向某个提交对象的指针。当执行<code>git commit</code>创建一个新的提交时，当前所在分支的指针会自动向前移动到新的提交上。</p>
<p><strong>2. HEAD 是你的位置。</strong></p>
<p><code>HEAD</code>是一个特殊的指针，指向你当前所在的分支。执行<code>git checkout</code>切换分支时，实际上是在移动<code>HEAD</code>指针，告诉 Git 你当前在哪个分支上工作。</p>
<p><strong>3. 提交历史是一张有向无环图。</strong></p>
<p>Git 的提交记录不是简单的线性列表，而是一张<strong>有向无环图</strong>。每个提交都持有指向其父提交的指针，当从某个节点分叉出新的分支并提交后，历史就形成了分叉的图结构。</p>
<hr />
<h1>3.2 举例：简单分支开发流程</h1>
<p>下面通过一个完整的例子，演示从创建仓库到多分支开发的完整流程。</p>
<blockquote>
<p>场景：项目主分支<code>master</code>已开发到<code>Commit 3</code>，现在需要开发 1.0 版本，因此从当前节点创建<code>1.0-dev</code>分支。</p>
</blockquote>
<h3>3.2.1 创建仓库并完成首次提交</h3>
<p>先手动创建一个 <code>README.md</code> 文件并创建仓库。</p>
<p><img src="2.webp" alt="第一步示意图" /></p>
<pre><code>git init
git add README.md
git commit -m "first commit"
</code></pre>
<p>初始状态下，<code>HEAD</code>指向<code>master</code>，<code>master</code>指向<code>Commit 1</code>。</p>
<p>此时提交历史：<code>Commit 1</code>（<code>master</code>, <code>HEAD</code>）</p>
<h3>3.2.2 在 master 上多次提交</h3>
<p>经过多次开发，<code>master</code>分支上共有三个提交，<code>master</code>指向最新的<code>Commit 3</code>。</p>
<p><img src="3.webp" alt="第二步示意图" /></p>
<pre><code># 两次开发提交
git add .
git commit -m "edit(readme): Commit 2"

git add .
git commit -m "edit(readme): Commit 3"
</code></pre>
<p>提交历史：<code>Commit 1</code> ← <code>Commit 2</code> ← <code>Commit 3</code>（<code>master</code>, <code>HEAD</code>）</p>
<h3>3.2.3 创建新分支</h3>
<p>基于当前<code>HEAD</code>指向的位置（即<code>master</code>的<code>Commit 3</code>），创建一个名为<code>1.0-dev</code>的新分支。</p>
<pre><code>git branch 1.0-dev
</code></pre>
<p>执行后，两个分支都指向同一个提交<code>Commit 3</code>。</p>
<h3>3.2.4 切换到新分支</h3>
<pre><code>git checkout 1.0-dev
</code></pre>
<p><code>HEAD</code>从<code>master</code>移动到<code>1.0-dev</code>，工作区切换到了开发分支上。</p>
<p><img src="4.webp" alt="第四步示意图" /></p>
<blockquote>
<p>注：③、④两步可合并为<code>git checkout -b 1.0-dev</code>，创建并切换一步到位。</p>
</blockquote>
<h3>3.2.5 在新分支上提交</h3>
<p>在<code>1.0-dev</code>分支上完成功能开发后提交：</p>
<pre><code>git add .
git commit -m "edit(readme): Commit 4"
</code></pre>
<p>此时<code>1.0-dev</code>向前移动到新的<code>Commit 4</code>，而<code>master</code>仍停留在<code>Commit 3</code>，历史出现分叉。</p>
<p><img src="5.webp" alt="第五步示意图" /></p>
<h3>3.2.6 切换回 master</h3>
<pre><code>git checkout master
</code></pre>
<p>切回<code>master</code>后，工作区文件恢复到<code>Commit 3</code>的状态，之前在新分支上做的修改暂时"隐藏"起来——这正是 Git 分支的<strong>隔离特性</strong>。</p>
<p><img src="6.webp" alt="第六步示意图" /></p>
<h3>3.2.7 在 master 上继续开发</h3>
<pre><code>git add .
git commit -m "edit(readme): Commit 5"
</code></pre>
<p><code>master</code>分支继续向前推进到<code>Commit 5</code>，与<code>1.0-dev</code>分支的差距进一步拉大。</p>
<p><img src="7.webp" alt="第七步示意图" /></p>
<h3>3.2.8 切换到 1.0-dev 开发</h3>
<p>切回开发分支，继续增加新功能：</p>
<pre><code>git checkout 1.0-dev
</code></pre>
<pre><code>git add .
git commit -m "edit(readme): Commit 6"
</code></pre>
<p><code>1.0-dev</code>分支推进到<code>Commit 6</code>。</p>
<p><img src="8.webp" alt="第八步示意图" /></p>
<p>此时提交历史结构如下：</p>
<pre><code>  C1 ← C2 ← C3 ← C5  (master)
                ↖
                  C4 ← C6  (1.0-dev, HEAD)
</code></pre>
<p><code>1.0-dev</code>分支的<code>README.md</code>内容:</p>
<pre><code># README

Commit 2

Commit 3

Commit 4

Commit 6
</code></pre>
<p><code>master</code>分支的<code>README.md</code>内容:</p>
<pre><code># README

Commit 2

Commit 3

Commit 5
</code></pre>
<hr />
<h1>3.3 命令行操作总结</h1>
<h3>3.3.1 查看分支列表</h3>
<pre><code>git branch
</code></pre>
<p>列出所有本地分支，当前所在分支前会标记<code>*</code>号。</p>
<pre><code>git branch -v
</code></pre>
<p>加上<code>-v</code>参数可同时查看每个分支的最新提交信息。</p>
<h3>3.3.2 创建分支</h3>
<pre><code>git branch &lt;branch-name&gt;
</code></pre>
<p>基于当前<code>HEAD</code>所在位置创建一个新分支。</p>
<h3>3.3.3 切换分支</h3>
<pre><code>git checkout &lt;branch-name&gt;
</code></pre>
<p>将<code>HEAD</code>切换到目标分支。</p>
<h3>3.3.4 创建并切换</h3>
<pre><code>git checkout -b &lt;branch-name&gt;
</code></pre>
<p>创建新分支后立即切换过去，是<code>git branch</code>加<code>git checkout</code>两步的合并写法。</p>
<hr />
<h1>3.4 在 VS Code 中进行分支操作</h1>
<p>VS Code 的源代码管理面板提供了直观的分支管理界面，无需记忆命令即可完成常见分支操作。</p>
<h3>3.4.1 查看当前分支</h3>
<p>在 VS Code 底部状态栏的左侧，可以看到当前所在的分支名称。点击分支名即可快速切换或创建分支。</p>
<h3>3.4.2 创建新分支</h3>
<ol>
<li>点击底部状态栏左侧的分支名。</li>
<li>在弹出的命令面板中选择<code>创建新分支...</code>。</li>
<li>输入新分支的名称，按回车确认。</li>
</ol>
<h3>3.4.3 切换分支</h3>
<ol>
<li>点击底部状态栏左侧的分支名。</li>
<li>在弹出的分支列表中，选择要切换到的目标分支。</li>
<li>工作区文件会自动更新为目标分支的内容。</li>
</ol>
<h3>3.4.4 在分支上提交</h3>
<p>在源代码管理面板中，暂存文件 → 填写提交信息 → 点击<code>提交</code>按钮。当前处于哪个分支，提交就会自动推进到哪个分支，无需额外操作。</p>
<h3>3.4.5 使用 Git Graph 查看分支图</h3>
<p>安装<code>Git Graph</code>插件后，点击源代码管理面板底部的<code>Git Graph</code>按钮，即可看到直观的分支图谱，清晰地展示所有分支的提交历史与分叉关系。</p>
<hr />
<h1>3.5 本节小结</h1>
<table>
<thead>
<tr>
<th>知识点</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>分支本质</td>
<td>指向提交的指针</td>
</tr>
<tr>
<td>HEAD</td>
<td>指向当前所在分支的指针</td>
</tr>
<tr>
<td>创建分支</td>
<td><code>git branch &lt;name&gt;</code> 或 <code>git checkout -b &lt;name&gt;</code></td>
</tr>
<tr>
<td>切换分支</td>
<td><code>git checkout &lt;name&gt;</code></td>
</tr>
<tr>
<td>分支隔离</td>
<td>不同分支的文件状态互不干扰</td>
</tr>
<tr>
<td>VS Code 操作</td>
<td>状态栏 → 分支名 → 创建 / 切换</td>
</tr>
</tbody>
</table>
<p>在本节中，掌握了以下内容：</p>
<ul>
<li>理解了 Git 分支的本质是指针，<code>HEAD</code>代表当前位置</li>
<li>通过完整的分支开发例子，理清了创建、切换、提交的完整流程</li>
<li>掌握了命令行的分支操作命令</li>
<li>学会了在 VS Code 中通过图形界面管理分支</li>
</ul>
<p>下一节我们将学习合并分支。</p>
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="信息学"/>
  </entry>
  <entry>
    <title>Git｜合并分支</title>
    <link href="https://luq-blog.xyz/posts/2026-06-22-git-notes-04-merge/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-06-22-git-notes-04-merge/</id>
    <published>2026-06-22T00:00:00.000Z</published>
    <updated>2026-06-22T00:00:00.000Z</updated>
    <summary>学习 Git 的两种合并方式：普通合并（merge）与快进合并（fast-forward），掌握分支合并的完整流程与常见场景。</summary>
    <content type="html"><![CDATA[<h1>4.0 本节命令行汇总</h1>
<pre><code>git checkout master          # 切换到目标分支
git merge 1.0-dev           # 将指定分支合并到当前分支
git merge --no-ff 1.0-dev   # 合并时禁止快进，强制创建合并提交
git config --global merge.ff false   # 全局设置：合并时默认禁止快进
git add &lt;file&gt;              # 暂存冲突解决后的文件
git commit                  # 完成冲突解决
</code></pre>
<hr />
<h1>4.1 合并分支的基本概念</h1>
<p>在上一节中，我们学习了如何创建和切换分支。当在不同分支上完成开发后，需要将分支的改动合并到一起。本节将学习 Git 的两种合并方式。</p>
<p><strong>合并的本质：</strong></p>
<p>将一个分支的提交历史"拼接"到另一个分支上。合并后，目标分支会新增一个合并提交，记录两条分支的交汇点。</p>
<hr />
<h1>4.2 第一种合并：普通合并（merge）</h1>
<p>普通合并适用于两条分支都有新的提交、且提交历史产生分叉的场景。</p>
<h3>4.2.1 合并场景</h3>
<blockquote>
<p>场景：项目主分支<code>master</code>在<code>Commit 3</code>处创建了<code>1.0-dev</code>分支。之后，<code>master</code>继续开发到<code>Commit 5</code>，<code>1.0-dev</code>也完成了1.0版本开发到<code>Commit 4</code>。现在需要将<code>1.0-dev</code>的功能合并回<code>master</code>。</p>
</blockquote>
<h3>4.2.2 合并操作</h3>
<p>首先切换到目标分支（<code>master</code>），然后执行合并：</p>
<pre><code>git checkout master
git merge 1.0-dev
</code></pre>
<p><img src="1.webp" alt="第一步示意图" /></p>
<h3>4.2.3 合并结果</h3>
<p>执行合并后，<code>master</code>分支会新增一个<strong>合并提交</strong>（Merge commit），这个提交有两个父提交，分别指向合并前的两个分支的最新提交。</p>
<h3>4.2.4 合并冲突</h3>
<p>VS Code 提供了图形化的合并操作界面。</p>
<ol>
<li>点击底部状态栏左侧的分支名</li>
<li>在弹出的分支列表中，选择要合并到当前分支的目标分支</li>
<li>右键点击目标分支，选择<code>将 &lt;分支名&gt; 合并到 &lt;当前分支&gt;</code></li>
<li>或者在源代码管理面板中，点击<code>...</code>菜单，选择<code>合并分支</code>，然后选择目标分支</li>
</ol>
<p><img src="2.webp" alt="第二步示意图" /></p>
<p><img src="3.webp" alt="第三步示意图" /></p>
<p>如果两个分支对同一文件的同一位置进行了不同的修改，Git 无法自动合并，此时会产生<strong>合并冲突</strong>。VS Code 会用特殊的标记显示冲突内容，需要手动选择保留哪一方的改动。</p>
<p><img src="4.webp" alt="第四步示意图" /></p>
<p>合并冲突的标记格式如下：</p>
<pre><code>&lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD
当前分支的内容
=======
被合并分支的内容
&gt;&gt;&gt;&gt;&gt;&gt;&gt; &lt;分支名&gt;
</code></pre>
<p>手动解决冲突后，保存文件，然后执行：</p>
<pre><code>git add &lt;file&gt;
git commit
</code></pre>
<p>完成冲突解决。</p>
<p>合并完成后，<code>1.0-dev</code>分支的改动已经被完整地合并到了<code>master</code>中。</p>
<p><img src="5.webp" alt="结果示意图" /></p>
<hr />
<h1>4.3 第二种合并：快进合并（fast-forward）</h1>
<p>快进合并发生在目标分支没有任何新提交、仅仅是被"甩在后面"的场景下。此时 Git 会直接将指针向前移动，而不创建新的合并提交。</p>
<h3>4.3.1 快进场景</h3>
<blockquote>
<p>场景：项目主分支<code>master</code>在<code>Commit 3</code>处创建了<code>1.0-dev</code>分支。之后，只有<code>1.0-dev</code>有新的提交（<code>Commit 4</code>），而<code>master</code>没有任何新提交。这种情况下，从<code>master</code>合并<code>1.0-dev</code>时就会触发快进合并。</p>
</blockquote>
<p>由于<code>master</code>没有任何新提交，Git 会"快进"指针，直接将<code>master</code>移动到<code>1.0-dev</code>所在的位置。这个过程不会创建新的合并提交，历史仍然是线性的。</p>
<p><img src="6.webp" alt="版本回退示意图" /></p>
<p>如果希望保留分支的完整历史记录，可以在合并时添加<code>--no-ff</code>参数，强制创建合并提交：</p>
<pre><code>git merge --no-ff 1.0-dev
</code></pre>
<p>或者直接在配置文件中更改，这是一劳永逸的方法：</p>
<pre><code>git config --global merge.ff false
</code></pre>
<p><img src="7.webp" alt="示意图" /></p>
<hr />
<h1>4.4 本节小结</h1>
<table>
<thead>
<tr>
<th>知识点</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>普通合并（merge）</td>
<td>两条分支都有提交时，创建合并提交</td>
</tr>
<tr>
<td>快进合并（fast-forward）</td>
<td>目标分支无新提交时，直接移动指针</td>
</tr>
<tr>
<td><code>--no-ff</code> 参数</td>
<td>强制创建合并提交，保留分支历史</td>
</tr>
<tr>
<td>合并冲突</td>
<td>同一文件同一位置的不同修改需要手动解决</td>
</tr>
</tbody>
</table>
<p>本节完成了以下内容：</p>
<ul>
<li>理解了 Git 合并的基本概念</li>
<li>掌握了普通合并（merge）的完整流程</li>
<li>学会了快进合并（fast-forward）的原理与<code>--no-ff</code>参数的使用</li>
<li>了解了在 VS Code 中进行合并操作的方法</li>
<li>认识了合并冲突的产生原因与解决方法</li>
</ul>
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="信息学"/>
  </entry>
  <entry>
    <title>Git｜创建本地仓库</title>
    <link href="https://luq-blog.xyz/posts/2026-06-21-git-notes-02-local-repo/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-06-21-git-notes-02-local-repo/</id>
    <published>2026-06-21T00:00:00.000Z</published>
    <updated>2026-06-21T00:00:00.000Z</updated>
    <summary>本节将学习如何使用命令行创建一个本地 Git 仓库，掌握文件的四种状态与状态间的转换操作，并了解提交信息的规范写法。</summary>
    <content type="html"><![CDATA[<h1>2.0 本节命令行汇总</h1>
<pre><code>git init                                   # 创建本地 Git 仓库
git add &lt;file&gt;                             # 暂存指定文件
git add .                                  # 暂存所有变更文件
git commit -m "message"                    # 提交变更，message 为提交说明
git log                                   # 查看提交历史
git log --pretty=oneline --graph          # 以单行图形化方式查看提交历史
</code></pre>
<hr />
<h1>2.1 创建本地仓库</h1>
<p>在 Git 中，本地仓库本质上就是项目根目录下的 <code>.git</code> 目录。通过 <code>git init</code> 命令可以在任意目录中初始化一个新的 Git 仓库。</p>
<p>以一个空项目为例。</p>
<p>在项目根目录下执行初始化命令：</p>
<pre><code>git init
</code></pre>
<p>执行成功后，项目目录中会生成一个 <code>.git</code> 文件夹，其中包含了仓库的所有版本记录和配置信息。可以理解为：<code>.git</code> 目录 = Git 仓库。</p>
<hr />
<h1>2.2 文件的四种状态</h1>
<p>在 Git 中，文件共有四种状态。理解这四种状态是掌握 Git 工作流程的关键。</p>
<table>
<thead>
<tr>
<th>状态</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Untracked</code> 未跟踪</td>
<td>Git 仓库中还没有记录的文件，属于新文件</td>
</tr>
<tr>
<td><code>Unmodified</code> 未修改</td>
<td>已被 Git 跟踪，且文件内容与仓库中记录的一致，没有改动</td>
</tr>
<tr>
<td><code>Modified</code> 已修改</td>
<td>已被 Git 跟踪，但文件内容已被修改，与仓库记录不一致</td>
</tr>
<tr>
<td><code>Staged</code> 已暂存</td>
<td>已被 Git 跟踪，且修改后的文件已被标记为"准备提交到仓库"的状态</td>
</tr>
</tbody>
</table>
<p><strong>状态流转关系：</strong></p>
<ul>
<li><code>Untracked</code> → <code>Staged</code>：执行 <code>git add</code>，添加文件到暂存区</li>
<li><code>Unmodified</code> → <code>Modified</code>：编辑文件内容，变为已修改状态</li>
<li><code>Modified</code> → <code>Staged</code>：执行 <code>git add</code>，将修改内容加入暂存区</li>
<li><code>Staged</code> → <code>Unmodified</code>：执行 <code>git commit</code>，提交后仓库记录更新，文件回到未修改状态</li>
<li><code>Unmodified</code> → <code>Untracked</code>：通过 <code>.gitignore</code> 或删除跟踪操作，使文件回到未跟踪状态</li>
</ul>
<hr />
<h1>2.3 首次提交</h1>
<p>初始化仓库后，通常需要先进行一次提交来建立版本基线。</p>
<p><strong>暂存文件：</strong></p>
<pre><code>git add README.md
</code></pre>
<p><code>git add</code> 命令将指定文件加入暂存区。如果需要暂存所有文件，可以使用 <code>git add .</code>。</p>
<p><strong>提交文件：</strong></p>
<pre><code>git commit -m "first commit"
</code></pre>
<p><code>git commit</code> 将暂存区中的内容提交到本地仓库。<code>-m</code> 参数后跟本次提交的说明信息。</p>
<p>首次提交完成后，文件状态会从 <code>Staged</code> 回到 <code>Unmodified</code>。</p>
<h3>2.3.1 在 VS Code 中进行提交</h3>
<p>VS Code 提供了图形化的提交操作，无需在终端输入命令即可完成暂存与提交。</p>
<p><strong>暂存文件：</strong></p>
<ol>
<li>
<p>打开源代码管理面板（按 <code>Ctrl + Shift + G</code>）</p>
</li>
<li>
<p>在 <code>更改</code> 列表中可以看到所有未暂存的文件</p>
</li>
<li>
<p>点击文件右侧的 <code>+</code> 按钮暂存单个文件，或点击文件列表上方的 <code>+</code> 按钮暂存所有文件</p>
</li>
</ol>
<p>暂存后，文件会从 <code>更改</code> 区域移动到 <code>暂存的更改</code> 区域，对应的状态由 <code>Modified</code> 变为 <code>Staged</code>。</p>
<p><strong>提交文件：</strong></p>
<ol>
<li>
<p>在源代码管理面板顶部的输入框中填写提交信息</p>
</li>
<li>
<p>点击输入框上方的 <code>提交</code> 按钮（或按 <code>Ctrl + Enter</code>），完成提交</p>
</li>
</ol>
<p>提交完成后，<code>暂存的更改</code> 区域会清空，文件状态回到 <code>Unmodified</code>。</p>
<p><strong>查看提交历史：</strong></p>
<p>在源代码管理面板底部，点击 <code>提交</code> 按钮旁边的 <code>...</code> 菜单，选择 <code>查看提交历史</code>，可以查看当前仓库的所有提交记录。</p>
<hr />
<h1>2.4 提交信息规范</h1>
<p>在团队协作中，统一的提交信息格式有助于代码审查和版本回溯。推荐使用以下规范格式：</p>
<pre><code>verb(range): detail
</code></pre>
<ul>
<li><code>verb</code>：动作动词，表示本次提交的操作类型</li>
<li><code>range</code>：影响范围或模块，用英文括号包裹</li>
<li><code>detail</code>：具体的提交描述内容</li>
</ul>
<p>常用动词示例如下：</p>
<table>
<thead>
<tr>
<th>动词</th>
<th>含义</th>
<th>示例</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>build</code></td>
<td>构建相关</td>
<td><code>build(config): 修改部署打包脚本</code></td>
</tr>
<tr>
<td><code>chore</code></td>
<td>杂项 / 工程变更</td>
<td><code>chore(deps): 安装 axios 依赖</code></td>
</tr>
<tr>
<td><code>docs</code></td>
<td>文档更新</td>
<td><code>docs(readme): 更新项目说明文档</code></td>
</tr>
<tr>
<td><code>edit</code></td>
<td>修改现有内容</td>
<td><code>edit(worker): 优化任务调度逻辑</code></td>
</tr>
<tr>
<td><code>feat</code></td>
<td>新增文件或功能</td>
<td><code>add(backend): 新增用户登录接口</code></td>
</tr>
<tr>
<td><code>fix</code></td>
<td>修复 Bug</td>
<td><code>fix(auth): 修复 Token 过期未刷新问题</code></td>
</tr>
<tr>
<td><code>perf</code></td>
<td>性能优化</td>
<td><code>perf(image): 压缩首页 Banner 图片</code></td>
</tr>
<tr>
<td><code>refactor</code></td>
<td>重构代码</td>
<td><code>refactor(utils): 拆分工具类，提高复用性</code></td>
</tr>
<tr>
<td><code>remove</code></td>
<td>删除文件或功能</td>
<td><code>remove(converter): 移除已废弃的格式转换模块</code></td>
</tr>
</tbody>
</table>
<hr />
<h1>2.5 查看提交记录</h1>
<p>提交完成后，可以使用 <code>git log</code> 命令查看仓库的提交历史。</p>
<p><strong>基础用法：</strong></p>
<pre><code>git log
</code></pre>
<p>输出结果示例：</p>
<pre><code>commit 7a89f743412273876184f0da11974d1e866a3114 (HEAD -&gt; master)
Author: your_name &lt;your_email@example.com&gt;
Date:   Wed Apr 29 22:13:33 2026 +0800

    edit(docs): README.md

commit 64a2b2accc0afa7c5419f79286892e8163407114
Author: your_name &lt;your_email@example.com&gt;
Date:   Wed Apr 29 21:16:09 2026 +0800

    first commit
</code></pre>
<p>每条记录包含提交哈希值、作者信息、提交时间和提交说明。</p>
<p><strong>格式化查看：</strong></p>
<pre><code>git log --pretty=oneline --graph
</code></pre>
<p>使用 <code>--pretty=oneline</code> 将每条提交压缩为一行显示，<code>--graph</code> 以可视化方式展示分支结构。</p>
<p>输出结果示例：</p>
<pre><code>* 7a89f743412273876184f0da11974d1e866a3114 (HEAD -&gt; master) edit(docs): README.md
* 64a2b2accc0afa7c5419f79286892e8163407114 first commit
</code></pre>
<hr />
<h1>2.6 本节小结</h1>
<p>本节完成了以下内容：</p>
<ul>
<li>学习了使用 <code>git init</code> 创建本地仓库的方法</li>
<li>理解了文件的四种状态（Untracked、Unmodified、Modified、Staged）及其流转关系</li>
<li>掌握了首次提交的完整流程（命令行与 VS Code 图形化操作）</li>
<li>了解了提交信息的规范格式与常用动词</li>
<li>学会了使用 <code>git log</code> 查看提交记录</li>
</ul>
<p>下一节我们将学习分支的创建与管理。</p>
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="信息学"/>
  </entry>
  <entry>
    <title>Git｜安装与配置</title>
    <link href="https://luq-blog.xyz/posts/2026-06-20-git-notes-01-env/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-06-20-git-notes-01-env/</id>
    <published>2026-06-20T00:00:00.000Z</published>
    <updated>2026-06-21T00:00:00.000Z</updated>
    <summary>在正式开始使用 Git 之前，需要先完成安装与基础配置。本节将介绍 Git 的安装方法、首次使用前的配置项，以及在 VS Code 中的使用方式。</summary>
    <content type="html"><![CDATA[<h1>1.0 本节命令行汇总</h1>
<pre><code>git --version                              # 验证 Git 安装是否成功
git config --global user.name "your_name" # 配置用户名
git config --global user.email "your_email@example.com"   # 配置邮箱
git config --global core.editor code      # 配置默认编辑器为 VS Code
git config --list                         # 查看当前所有配置
git init                                  # 初始化本地仓库
git add &lt;file&gt;                            # 暂存指定文件
git add .                                 # 暂存所有变更文件
git commit -m "message"                   # 提交变更，message 为提交说明
</code></pre>
<hr />
<h1>1.1 Git 的安装</h1>
<p>Git 是一款免费、开源的分布式版本控制系统，广泛用于代码管理和团队协作。在开始使用之前，需要先完成 Git 的安装。</p>
<p><strong>安装步骤：</strong></p>
<ol>
<li>
<p>访问 Git 官方网站：https://git-scm.com/downloads</p>
</li>
<li>
<p>根据你的操作系统（Windows、macOS 或 Linux）选择对应的安装包</p>
</li>
<li>
<p>下载完成后，双击安装包启动安装程序</p>
</li>
<li>
<p>按照安装向导的提示完成安装，建议保持默认选项即可</p>
</li>
</ol>
<p><strong>验证安装：</strong></p>
<p>安装完成后，打开命令行终端，输入以下命令验证安装是否成功：</p>
<pre><code>git --version
</code></pre>
<p>如果终端返回类似 <code>git version x.x.x</code> 的版本信息，说明 Git 安装成功。</p>
<hr />
<h1>1.2 首次使用前的配置</h1>
<p>Git 安装完成后，需要进行一些基础配置。这些配置会记录在每次提交的元数据中，方便协作时让其他开发者识别你的身份。</p>
<p><strong>配置用户名：</strong></p>
<pre><code>git config --global user.name "your_name"
</code></pre>
<p>将 <code>your_name</code> 替换为你的用户名。这个用户名会出现在每次提交的记录中。</p>
<p><strong>配置邮箱：</strong></p>
<pre><code>git config --global user.email "your_email@example.com"
</code></pre>
<p>将 <code>your_email@example.com</code> 替换为你的邮箱地址。此邮箱与 GitHub 账号无关，仅作为个人联系方式出现在提交信息中。</p>
<p><strong>配置默认编辑器：</strong></p>
<pre><code>git config --global core.editor code
</code></pre>
<p>将默认编辑器设置为 VS Code，这样在执行 <code>git commit</code> 等操作时，Git 会自动打开 VS Code 作为编辑器。</p>
<p><strong>验证配置：</strong></p>
<p>使用以下命令查看当前所有配置：</p>
<pre><code>git config --list
</code></pre>
<hr />
<h1>1.3 在 VS Code 中使用 Git</h1>
<p>VS Code 内置了 Git 支持，无需安装额外扩展即可使用基本的版本控制功能。</p>
<h3>1.3.1 打开源代码管理面板</h3>
<p>在 VS Code 中，点击左侧活动栏的 <code>源代码管理</code> 图标（或按快捷键 <code>Ctrl + Shift + G</code>），即可打开源代码管理面板。该面板会显示当前仓库的所有变更文件。</p>
<h3>1.3.2 基本操作流程</h3>
<p><strong>初始化仓库：</strong></p>
<ol>
<li>
<p>打开你的项目文件夹</p>
</li>
<li>
<p>在源代码管理面板中点击 <code>初始化仓库</code> 按钮</p>
</li>
<li>
<p>或在终端中执行以下命令：</p>
</li>
</ol>
<pre><code>git init
</code></pre>
<p><strong>暂存与提交：</strong></p>
<ol>
<li>
<p>修改文件后，在源代码管理面板中可以看到变更的文件列表</p>
</li>
<li>
<p>点击文件右侧的 <code>+</code> 按钮暂存单个文件，或点击文件列表上方的 <code>+</code> 按钮暂存所有文件</p>
</li>
<li>
<p>在输入框中填写提交信息，点击 <code>提交</code> 按钮完成提交</p>
</li>
</ol>
<p><strong>推送与拉取：</strong></p>
<ol>
<li>
<p>点击源代码管理面板底部的 <code>...</code> 按钮</p>
</li>
<li>
<p>选择 <code>推送</code> 或 <code>拉取</code> 进行远程仓库的同步操作</p>
</li>
</ol>
<hr />
<h1>1.4 Git Graph 插件的安装与使用</h1>
<p>Git Graph 是一款 VS Code 插件，可以可视化展示仓库的提交历史图谱，帮助更直观地理解分支结构与提交记录。</p>
<h3>1.4.1 安装 Git Graph</h3>
<ol>
<li>
<p>打开 VS Code，点击左侧活动栏的 <code>扩展</code> 图标（或按快捷键 <code>Ctrl + Shift + X</code>）</p>
</li>
<li>
<p>在扩展搜索框中输入 <code>Git Graph</code></p>
</li>
<li>
<p>在搜索结果中找到由 <code>mhutchie</code> 发布的 <code>Git Graph</code> 扩展，点击 <code>安装</code> 按钮</p>
</li>
</ol>
<p><img src="1.webp" alt="Git Graph 插件展示界面" /></p>
<h3>1.4.2 使用 Git Graph</h3>
<p>安装完成后，在源代码管理面板底部或 VS Code 状态栏中可以找到 <code>Git Graph</code> 的入口按钮。</p>
<p>点击后即可打开 Git Graph 视图，可以看到以下内容：</p>
<ul>
<li>仓库的分支结构与提交节点可视化图谱</li>
<li>每次提交的详细信息（作者、时间、提交信息）</li>
<li>分支之间的合并关系</li>
</ul>
<p><img src="2.webp" alt="Git Graph 界面" /></p>
<p><strong>常用操作：</strong></p>
<ul>
<li>点击提交节点可以查看该次提交的详细变更</li>
<li>右键提交节点可以进行创建分支、查看文件变更等操作</li>
<li>点击右上角的 <code>刷新</code> 按钮可以重新拉取最新的提交记录</li>
<li>点击 <code>齿轮</code> 图标可以进入插件设置，自定义图谱的展示样式</li>
</ul>
<hr />
<h1>1.5 本节小结</h1>
<p>本节完成了以下内容：</p>
<ul>
<li>介绍了 Git 的安装方法和验证步骤</li>
<li>说明了首次使用 Git 前的三项基础配置</li>
<li>讲解了如何在 VS Code 中使用 Git 进行基本的版本控制操作</li>
<li>介绍了 Git Graph 插件的安装方法与使用方式</li>
</ul>
<p>下一节我们将学习 Git 的核心——本地仓库的创建。</p>
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="信息学"/>
  </entry>
  <entry>
    <title>数据分析｜Jupyter在VSCode中的使用</title>
    <link href="https://luq-blog.xyz/posts/2026-06-06-data-notes-02-vsc/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-06-06-data-notes-02-vsc/</id>
    <published>2026-06-06T00:00:00.000Z</published>
    <updated>2026-06-12T00:00:00.000Z</updated>
    <summary>本节介绍如何在 VS Code 中安装和使用 Jupyter，包括扩展安装、创建文件和基本操作。</summary>
    <content type="html"><![CDATA[<h1>2.1 VS Code 的安装</h1>
<p>Visual Studio Code（简称 VS Code）是一款由微软开发的免费、开源的代码编辑器。它支持 Windows、macOS 和 Linux 操作系统，并且拥有丰富的扩展生态，可以很好地支持 Python 和 Jupyter 的开发工作。</p>
<p><strong>安装步骤：</strong></p>
<ol>
<li>
<p>访问 VS Code 官方网站：https://code.visualstudio.com/</p>
</li>
<li>
<p>点击页面中央的 <code>Download for Windows</code> 按钮（根据你的操作系统自动适配）</p>
</li>
<li>
<p>下载完成后，双击安装包启动安装程序</p>
</li>
<li>
<p>按照安装向导的提示完成安装，建议勾选以下选项：</p>
<ul>
<li><code>创建桌面快捷方式</code></li>
<li><code>将 '通过 Code 打开' 操作添加到文件上下文菜单</code></li>
<li><code>将 '通过 Code 打开' 操作添加到目录上下文菜单</code></li>
</ul>
</li>
<li>
<p>安装完成后，启动 VS Code</p>
</li>
</ol>
<p><strong>验证安装：</strong></p>
<p>VS Code 启动后，界面左侧是活动栏，中间是编辑器区域。如果看到欢迎界面，说明安装成功。</p>
<hr />
<h1>2.2 Jupyter 扩展的安装</h1>
<p>VS Code 本身并不直接支持 Jupyter，需要通过安装扩展来获得 Jupyter Notebook 的支持。</p>
<p><strong>安装 Python 扩展：</strong></p>
<ol>
<li>
<p>打开 VS Code，点击左侧活动栏的 <code>扩展</code> 图标（或按快捷键 <code>Ctrl + Shift + X</code>）</p>
</li>
<li>
<p>在扩展搜索框中输入 <code>Python</code></p>
</li>
<li>
<p>在搜索结果中找到由 Microsoft 发布的 <code>Python</code> 扩展，点击 <code>安装</code> 按钮</p>
</li>
</ol>
<p>安装此扩展后，VS Code 将获得以下功能：</p>
<ul>
<li>Python 语言支持（语法高亮、代码补全、调试等）</li>
<li>Jupyter Notebook 支持</li>
<li>与已安装 Python 环境的集成</li>
</ul>
<p><strong>安装 Jupyter 扩展：</strong></p>
<p><code>Python</code> 扩展已经包含了 Jupyter 的核心功能，无需单独安装 Jupyter 扩展。如果需要额外的 Jupyter 功能，可以在扩展市场中搜索 <code>Jupyter</code> 并安装相关扩展。</p>
<p><strong>配置 Python 解释器：</strong></p>
<ol>
<li>
<p>按 <code>Ctrl + Shift + P</code> 打开命令面板</p>
</li>
<li>
<p>输入 <code>Python: Select Interpreter</code> 并选择该命令</p>
</li>
<li>
<p>在列表中选择你安装的 Python 环境（通常是 Anaconda 环境）</p>
</li>
</ol>
<hr />
<h1>2.3 Jupyter Notebook 的简单使用</h1>
<h2>2.3.1 创建 Jupyter Notebook 文件</h2>
<p>在 VS Code 中使用 Jupyter Notebook 有两种方式：</p>
<p><strong>方式一：直接创建 <code>.ipynb</code> 文件</strong></p>
<ol>
<li>
<p>点击 <code>文件</code> -&gt; <code>新建文件</code>（或按 <code>Ctrl + N</code>）</p>
</li>
<li>
<p>在编辑器中输入文件名，文件后缀为 <code>.ipynb</code>，例如 <code>demo.ipynb</code></p>
</li>
<li>
<p>文件创建后，VS Code 会自动识别为 Jupyter Notebook 文件</p>
</li>
</ol>
<p><strong>方式二：通过命令面板创建</strong></p>
<ol>
<li>
<p>按 <code>Ctrl + Shift + P</code> 打开命令面板</p>
</li>
<li>
<p>输入 <code>Jupyter: Create New Jupyter Notebook</code> 并选择该命令</p>
</li>
</ol>
<h2>2.3.2 编写和运行代码</h2>
<p>创建 Notebook 文件后，可以看到类似 Jupyter Notebook 的界面，包含多个 Cell。</p>
<p><strong>添加 Cell：</strong></p>
<ul>
<li>点击工具栏的 <code>+ Code</code> 按钮添加代码 Cell</li>
<li>点击工具栏的 <code>+ Markdown</code> 按钮添加 Markdown Cell</li>
</ul>
<p><strong>编写代码：</strong></p>
<p>在 Cell 中输入 Python 代码，例如：</p>
<pre><code>print("Hello, VS Code with Jupyter!")
</code></pre>
<p><strong>运行 Cell：</strong></p>
<ul>
<li>点击 Cell 左侧的运行按钮</li>
<li>使用快捷键 <code>Shift + Enter</code> 运行当前 Cell 并跳转到下一个 Cell</li>
<li>使用快捷键 <code>Ctrl + Enter</code> 运行当前 Cell</li>
</ul>
<p>运行代码后，Cell 下方会显示输出结果。</p>
<h2>2.3.3 使用交互式窗口</h2>
<p>VS Code 还支持交互式 Python 窗口，类似于 Jupyter 的 REPL 环境。</p>
<p><strong>打开交互式窗口：</strong></p>
<ol>
<li>
<p>右键点击 Python 文件中的代码</p>
</li>
<li>
<p>选择 <code>Run Current File in Interactive Window</code></p>
</li>
</ol>
<p>或者：</p>
<ol>
<li>
<p>按 <code>Ctrl + Shift + P</code> 打开命令面板</p>
</li>
<li>
<p>输入 <code>Jupyter: Create Interactive Window</code> 并选择该命令</p>
</li>
</ol>
<p>在交互式窗口中，可以逐行输入代码并立即看到执行结果，非常适合快速测试和实验。</p>
<h2>2.3.4 常用快捷键</h2>
<p>在 VS Code 中使用 Jupyter 时，以下快捷键可以提高效率：</p>
<table>
<thead>
<tr>
<th>快捷键</th>
<th>功能</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Shift + Enter</code></td>
<td>运行当前 Cell 并跳转到下一个 Cell</td>
</tr>
<tr>
<td><code>Ctrl + Enter</code></td>
<td>运行当前 Cell</td>
</tr>
<tr>
<td><code>Alt + Enter</code></td>
<td>运行当前 Cell 并在下方插入新 Cell</td>
</tr>
<tr>
<td><code>Esc</code></td>
<td>进入命令模式</td>
</tr>
<tr>
<td><code>Enter</code></td>
<td>进入编辑模式</td>
</tr>
<tr>
<td><code>DD</code></td>
<td>删除当前 Cell（连续按两次 D）</td>
</tr>
<tr>
<td><code>A</code></td>
<td>在当前 Cell 上方插入新 Cell</td>
</tr>
<tr>
<td><code>B</code></td>
<td>在当前 Cell 下方插入新 Cell</td>
</tr>
<tr>
<td><code>M</code></td>
<td>将当前 Cell 转换为 Markdown 格式</td>
</tr>
<tr>
<td><code>Y</code></td>
<td>将当前 Cell 转换为代码格式</td>
</tr>
</tbody>
</table>
<hr />
<h1>2.4 本节小结</h1>
<p>本节完成了以下内容：</p>
<ul>
<li>介绍了 VS Code 的安装方法</li>
<li>说明了如何在 VS Code 中安装 Python 和 Jupyter 扩展</li>
<li>讲解了 Jupyter Notebook 在 VS Code 中的基本使用，包括创建文件、编写代码、运行 Cell 和交互式窗口的使用</li>
</ul>
<p>下一节我们将学习 NumPy 的基本用法。</p>
<hr />
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="信息学"/>
  </entry>
  <entry>
    <title>数据分析｜开发环境的配置</title>
    <link href="https://luq-blog.xyz/posts/2026-06-05-data-notes-01-env/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-06-05-data-notes-01-env/</id>
    <published>2026-06-05T00:00:00.000Z</published>
    <updated>2026-06-12T00:00:00.000Z</updated>
    <summary>进行 Python 数据分析之前，需要先搭建好开发环境。本节将介绍 Anaconda 的安装与 Jupyter Notebook 的基本使用方法。</summary>
    <content type="html"><![CDATA[<h1>1.1 Anaconda 的安装</h1>
<p>Anaconda 是一个开源的 Python 发行版，它包含了 Python 解释器以及科学计算所需的常用库（如 NumPy、Pandas、Matplotlib 等）。使用 Anaconda 可以省去单独安装各个库的麻烦，是数据分析工作的首选开发环境。</p>
<p><strong>安装步骤：</strong></p>
<ol>
<li>
<p>访问 Anaconda 官方网站：https://www.anaconda.com/download</p>
</li>
<li>
<p>根据你的操作系统（Windows、macOS 或 Linux）选择对应的安装包</p>
</li>
<li>
<p>下载完成后，双击安装包启动安装程序</p>
</li>
<li>
<p>按照安装向导的提示完成安装，建议选择"为所有用户安装"选项</p>
</li>
<li>
<p>安装过程中，保持 <code>Add Anaconda to PATH</code> 选项为默认状态</p>
</li>
</ol>
<p><strong>验证安装：</strong></p>
<p>安装完成后，打开命令行终端（Windows 用户使用 <code>Anaconda Prompt</code>），输入以下命令验证安装是否成功：</p>
<pre><code>conda --version
</code></pre>
<p>如果终端返回类似 <code>conda x.x.x</code> 的版本信息，说明 Anaconda 安装成功。</p>
<hr />
<h1>1.2 Jupyter Notebook 的安装与启动</h1>
<p>Jupyter Notebook 是一个交互式的 Web 应用程序，允许用户创建和共享包含代码、公式、可视化和文本的文档。它是数据分析工作中最常用的开发工具之一。</p>
<p><strong>安装说明：</strong></p>
<p>Jupyter Notebook 已经集成在 Anaconda 中，无需单独安装。如果你使用的是完整的 Anaconda 安装包，Jupyter Notebook 已经预装好了。</p>
<p>如果需要手动安装或更新，可以在终端中执行以下命令：</p>
<pre><code>conda install jupyter notebook
</code></pre>
<p><strong>启动方式：</strong></p>
<ol>
<li>
<p>通过 <code>Anaconda Navigator</code> 启动：</p>
<ul>
<li>打开 <code>Anaconda Navigator</code> 应用</li>
<li>在主界面找到 <code>Jupyter Notebook</code> 卡片</li>
<li>点击 <code>Launch</code> 按钮启动</li>
</ul>
</li>
<li>
<p>通过命令行启动：</p>
<ul>
<li>打开终端（<code>Anaconda Prompt</code>）</li>
<li>进入你想要保存工作文件的目录</li>
<li>执行以下命令：</li>
</ul>
</li>
</ol>
<pre><code>cd /path/to/your/workspace
jupyter notebook
</code></pre>
<p>执行命令后，浏览器会自动打开 Jupyter Notebook 的工作界面，默认访问地址为 http://localhost:8888 。</p>
<hr />
<h1>1.3 Jupyter Notebook 初步使用</h1>
<h2>1.3.1 创建新文件</h2>
<p>在 Jupyter Notebook 主界面中，可以看到当前目录下的文件和文件夹。点击右上角的 <code>New</code> 按钮，然后选择 <code>Python 3</code> 即可创建一个新的 Notebook 文件。</p>
<p>新创建的文件会打开一个新的标签页，界面主要包含以下部分：</p>
<ul>
<li>顶部：文件名和菜单栏</li>
<li>中部：代码编辑区域（称为 Cell）</li>
<li>顶部工具栏：常用操作按钮</li>
</ul>
<h2>1.3.2 创建与使用 Cell</h2>
<p>Cell 是 Jupyter Notebook 的基本工作单元，每个 Cell 可以包含代码、Markdown 文本或原始文本。</p>
<p><strong>添加 Cell：</strong></p>
<ul>
<li>点击菜单栏的 <code>Insert</code> -&gt; <code>Insert Cell Below</code> 在当前 Cell 下方插入新 Cell</li>
<li>点击工具栏中的 <code>+</code> 按钮添加 Cell</li>
<li>使用快捷键 <code>B</code> 在当前 Cell 下方插入新 Cell</li>
</ul>
<p><strong>编写代码：</strong></p>
<p>在 Cell 中输入 Python 代码，例如：</p>
<pre><code>print("Hello, Jupyter Notebook!")
</code></pre>
<p><strong>运行 Cell：</strong></p>
<ul>
<li>点击工具栏的 <code>Run</code> 按钮运行当前 Cell</li>
<li>使用快捷键 <code>Shift + Enter</code> 运行当前 Cell 并跳转到下一个 Cell</li>
<li>使用快捷键 <code>Ctrl + Enter</code> 运行当前 Cell 但不跳转</li>
</ul>
<p>运行代码后，Cell 下方会显示输出结果。</p>
<p><strong>Cell 类型切换：</strong></p>
<p>每个 Cell 可以设置为不同类型，通过工具栏的下拉菜单或快捷键切换：</p>
<ul>
<li><code>Code</code>：编写和运行 Python 代码</li>
<li><code>Markdown</code>：编写格式化文本和说明文档</li>
<li><code>Raw</code>：原始文本，不进行任何格式化处理</li>
</ul>
<h2>1.3.3 常用快捷键</h2>
<p>掌握快捷键可以显著提高工作效率。以下是 Jupyter Notebook 中最常用的快捷键：</p>
<table>
<thead>
<tr>
<th>快捷键</th>
<th>功能</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Shift + Enter</code></td>
<td>运行当前 Cell 并跳转到下一个 Cell</td>
</tr>
<tr>
<td><code>Ctrl + Enter</code></td>
<td>运行当前 Cell</td>
</tr>
<tr>
<td><code>Alt + Enter</code></td>
<td>运行当前 Cell 并在下方插入新 Cell</td>
</tr>
<tr>
<td><code>A</code></td>
<td>在当前 Cell 上方插入新 Cell</td>
</tr>
<tr>
<td><code>B</code></td>
<td>在当前 Cell 下方插入新 Cell</td>
</tr>
<tr>
<td><code>DD</code></td>
<td>删除当前 Cell（连续按两次 D）</td>
</tr>
<tr>
<td><code>M</code></td>
<td>将当前 Cell 转换为 Markdown 格式</td>
</tr>
<tr>
<td><code>Y</code></td>
<td>将当前 Cell 转换为代码格式</td>
</tr>
<tr>
<td><code>L</code></td>
<td>显示或隐藏当前 Cell 的行号</td>
</tr>
<tr>
<td><code>O</code></td>
<td>显示或隐藏当前 Cell 的输出</td>
</tr>
<tr>
<td><code>Z</code></td>
<td>撤销删除的 Cell</td>
</tr>
</tbody>
</table>
<p><strong>使用技巧：</strong></p>
<ol>
<li>
<p>进入命令模式后（点击 Cell 左侧空白区域或按 <code>Esc</code> 键），可以快速使用上述快捷键</p>
</li>
<li>
<p>按 <code>H</code> 键可以打开快捷键帮助面板</p>
</li>
<li>
<p>编辑模式下（点击 Cell 内部或按 <code>Enter</code> 键），可以正常编辑代码</p>
</li>
</ol>
<hr />
<h1>1.4 本节小结</h1>
<p>本节完成了以下内容：</p>
<ul>
<li>介绍了 Anaconda 的安装方法和验证步骤</li>
<li>说明了 Jupyter Notebook 的启动方式</li>
<li>讲解了 Jupyter Notebook 的基本操作，包括文件创建、Cell 使用和常用快捷键</li>
</ul>
<p>下一节我们将学习 Jupyter 在 VS Code 中的安装方法。</p>
<hr />
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="信息学"/>
  </entry>
  <entry>
    <title>Arduino｜简易音乐播放器</title>
    <link href="https://luq-blog.xyz/posts/2026-05-28-beep-audio/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-05-28-beep-audio/</id>
    <published>2026-05-28T00:00:00.000Z</published>
    <updated>2026-06-21T00:00:00.000Z</updated>
    <summary>一个有趣的 Arduino 小项目——使用无源蜂鸣器和 OLED 屏幕，完整播放《神のまにまに》（神的随波逐流）。</summary>
    <content type="html"><![CDATA[<h1>1. 前言</h1>
<p>大家好，今天给大家带来一个有趣的 Arduino 小项目——使用无源蜂鸣器和 OLED 屏幕，完整播放《神のまにまに》（神的随波逐流）这首经典歌曲。</p>
<p>项目核心功能：</p>
<ul>
<li>通过 PWM 调波技术驱动无源蜂鸣器播放音乐</li>
<li>OLED 屏幕同步显示歌曲封面</li>
<li>完整乐谱数据，支持歌曲循环播放</li>
</ul>
<blockquote>
<p><strong>演示视频</strong>：</p>
<p>&lt;iframe width="100%" height="468" src="//player.bilibili.com/player.html?isOutside=true&amp;aid=116069712789890&amp;bvid=BV1kcZJBiE3U&amp;cid=36065446553&amp;p=1" scrolling="no" autoplay=0 frameborder="no" framespacing="0" allowfullscreen="true"&gt;&lt;/iframe&gt;</p>
</blockquote>
<hr />
<h1>2. 简介</h1>
<p>本项目使用 Arduino 开发板驱动无源蜂鸣器播放《神のまにまに》（神的随波逐流），同时在 OLED 屏幕上显示歌曲封面图片。通过 PWM 调波技术实现音调控制，支持完整的歌曲循环播放结构。</p>
<h2>2.1 硬件清单</h2>
<table>
<thead>
<tr>
<th>器件</th>
<th>规格</th>
<th>数量</th>
</tr>
</thead>
<tbody>
<tr>
<td>Arduino 主控板</td>
<td>ESP8266 等</td>
<td>1</td>
</tr>
<tr>
<td>无源蜂鸣器</td>
<td>3~5V 无源</td>
<td>1</td>
</tr>
<tr>
<td>OLED 屏幕</td>
<td>0.96寸 I2C 128×64 SSD1306</td>
<td>1</td>
</tr>
<tr>
<td>杜邦线</td>
<td>根据开发板而定</td>
<td>若干</td>
</tr>
</tbody>
</table>
<h2>2.2 接线说明</h2>
<pre><code>Arduino        无源蜂鸣器
   GPIO 1  ────────────  Signal

Arduino        OLED屏幕
   GPIO 4  ────────────  SDA
   GPIO 5  ────────────  SCL
   3V/5V   ────────────  VCC
   GND     ────────────  GND
</code></pre>
<hr />
<table>
<thead>
<tr>
<th>OLED 引脚</th>
<th>Arduino 引脚</th>
</tr>
</thead>
<tbody>
<tr>
<td>SDA</td>
<td>A4 (SDA)</td>
</tr>
<tr>
<td>SCL</td>
<td>A5 (SCL)</td>
</tr>
<tr>
<td>VCC</td>
<td>3V/5V</td>
</tr>
<tr>
<td>GND</td>
<td>GND</td>
</tr>
</tbody>
</table>
<h2>2.3 软件依赖</h2>
<p>本项目使用以下开源库：</p>
<table>
<thead>
<tr>
<th>库名</th>
<th>版本</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="https://github.com/adafruit/Adafruit-GFX-Library">Adafruit GFX Library</a></td>
<td>≥1.10</td>
<td>图形库核心</td>
</tr>
<tr>
<td><a href="https://github.com/adafruit/Adafruit_SSD1306">Adafruit SSD1306</a></td>
<td>≥2.5</td>
<td>OLED 驱动库</td>
</tr>
<tr>
<td>SPI</td>
<td>内置</td>
<td>SPI 通信库</td>
</tr>
<tr>
<td>Wire</td>
<td>内置</td>
<td>I2C 通信库</td>
</tr>
</tbody>
</table>
<h3>2.3.1 库安装方法</h3>
<p>在 VS Code 中的 PlatformIO 插件搜索并安装：</p>
<ul>
<li><code>Adafruit GFX Library</code> by Adafruit</li>
<li><code>Adafruit SSD1306</code> by Adafruit</li>
</ul>
<h1>3. 技术原理</h1>
<h2>3.1 PWM 调波发声原理</h2>
<p>无源蜂鸣器需要外部电路提供交流脉冲信号才能发声。通过 Arduino 的 <code>tone()</code> 函数，我们可以：</p>
<ul>
<li><strong>生成指定频率的方波信号</strong>：不同频率对应不同音高</li>
<li><strong>PWM 占空比固定为 50%</strong>：确保信号稳定</li>
<li><strong>音调与频率对应关系</strong>：
<ul>
<li>低音：262Hz (C4) ~ 494Hz (B4)</li>
<li>中音：523Hz (C5) ~ 988Hz (B5)</li>
</ul>
</li>
</ul>
<pre><code>┌─────────────┐
│  Arduino    │ ──tone(440Hz)──▶ [蜂鸣器] ──▶ 发出 A4 音
└─────────────┘
</code></pre>
<h2>3.2 乐谱数据结构设计</h2>
<p>本项目采用<strong>双数组</strong>存储乐谱信息：</p>
<table>
<thead>
<tr>
<th>数组</th>
<th>作用</th>
<th>数据示例</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>musicSentenceTones</code></td>
<td>音调序列</td>
<td><code>-5, -6, 1, 2, 2, 1...</code></td>
</tr>
<tr>
<td><code>musicSentenceTime</code></td>
<td>时长序列</td>
<td><code>4, 4, 4, 3, 1...</code></td>
</tr>
</tbody>
</table>
<p><strong>音调编码规则</strong>：</p>
<pre><code>// 对照规则：低音 -1~-7; 中音 1~7; 高音 11~17; 2# 为 33
int lowTones[8]  = {0, 262, 294, 330, 349, 392, 440, 494};      // 低音频率
int midTones[8]  = {0, 523, 587, 659, 698, 784, 880, 988};      // 中音频率
int mid2sTone    = 622;                                          // 中音 2# 频率
</code></pre>
<h2>3.3 OLED 显示原理</h2>
<p>使用 <strong>Adafruit_SSD1306</strong> 库驱动 128×64 分辨率的 OLED 屏幕：</p>
<ul>
<li><strong>通信协议</strong>：I2C（只占用 2 个 GPIO 引脚）</li>
<li><strong>显示方式</strong>：位图数组直接绘制</li>
<li><strong>显存操作</strong>：双缓冲避免闪烁</li>
</ul>
<pre><code>OLED 引脚连接：
┌─────────────┐
│  Arduino    │
│    GPIO 5  ────▶ SCL (时钟线)
│    GPIO 4  ────▶ SDA (数据线)
└─────────────┘
</code></pre>
<hr />
<h1>4. 代码展示</h1>
<p>::github{repo="Luquiescene/kami-no-manimani-music-player-with-Arduino"}</p>
<hr />
<h1>5. 算法解读</h1>
<h2>5.1 循环控制机制</h2>
<p>本项目的循环是通过 <code>breakPoint</code> 变量控制歌曲的反复段：</p>
<pre><code>歌曲结构：
├── 前奏（0-6行）
├── A段（7-14行）
├── B段（15-32行）
├── 反复句1（33行）→ 回到前奏
├── 反复句2（34-38行）→ 回到B段
└── 结束句（39-51行）
</code></pre>
<p><strong>断点控制逻辑</strong>：</p>
<pre><code>breakPoint = 0 → 到达第33行时，反复到第6行
breakPoint = 1 → 到达第38行时，反复到第19行
breakPoint = 2 → 到达第32行时，进入结束句
</code></pre>
<h2>5.2 时长计算公式</h2>
<pre><code>thisTime = SIXTEENTH_NOTE * musicSentenceTime[rows][columns] - 40;
</code></pre>
<ul>
<li><strong>基准单位</strong>：十六分音符（111ms）</li>
<li><strong>时长修正</strong>：乘以节拍数，再减去 40ms 作为音间间隔</li>
<li><strong>为什么要减40ms</strong>：为下一个音符的 <code>noTone()</code> 留出间隙</li>
</ul>
<h2>5.3 音调编码映射</h2>
<table>
<thead>
<tr>
<th>编码值</th>
<th>实际音高</th>
<th>计算方式</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>-1</code> ~ <code>-7</code></td>
<td>低音 1~7</td>
<td><code>lowTones[abs(编码)]</code></td>
</tr>
<tr>
<td><code>1</code> ~ <code>7</code></td>
<td>中音 1~7</td>
<td><code>midTones[编码]</code></td>
</tr>
<tr>
<td><code>33</code></td>
<td>中音 2#</td>
<td>特殊值，查表 <code>mid2sTone</code></td>
</tr>
<tr>
<td><code>0</code></td>
<td>休止符</td>
<td>无操作（需配合下一音判断）</td>
</tr>
</tbody>
</table>
<hr />
<h1>6. 总结</h1>
<h2>6.1 项目亮点</h2>
<p><s>其实是没有亮点，但是博主牵强附合（</s></p>
<ol>
<li><strong>资源占用少</strong>：仅使用 2 个 GPIO 引脚（I2C 通信）</li>
<li><strong>代码结构清晰</strong>：数据与逻辑分离，便于维护和扩展</li>
<li><strong>支持循环播放</strong>：通过断点机制实现复杂的歌曲结构</li>
</ol>
<h2>6.2 硬件清单</h2>
<table>
<thead>
<tr>
<th>器件</th>
<th>数量</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>ESP8266 开发板</td>
<td>1</td>
<td>主控芯片</td>
</tr>
<tr>
<td>无源蜂鸣器</td>
<td>1</td>
<td>发声元件</td>
</tr>
<tr>
<td>OLED 屏幕 (SSD1306)</td>
<td>1</td>
<td>128×64 I2C 接口</td>
</tr>
<tr>
<td>杜邦线</td>
<td>若干</td>
<td>连接线路</td>
</tr>
</tbody>
</table>
<h2>6.3 可扩展方向</h2>
<p><s>实际情况是博主技术力不够，实现不了</s></p>
<ul>
<li>添加按钮实现"播放/暂停"功能</li>
<li>增加音量调节</li>
<li>支持多首歌曲切换</li>
<li>添加歌词同步显示功能</li>
</ul>
<hr />
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="信息学"/>
  </entry>
  <entry>
    <title>备份工具｜FreeFileSync</title>
    <link href="https://luq-blog.xyz/posts/2026-05-16-freefilesync/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-05-16-freefilesync/</id>
    <published>2026-05-16T00:00:00.000Z</published>
    <updated>2026-06-12T00:00:00.000Z</updated>
    <summary>推荐一款开源的文件备份工具。</summary>
    <content type="html"><![CDATA[<h1>1. 前言</h1>
<p>开发项目时经常需要物理备份工程文件，以防数据意外丢失。虽然系统自带文件比对的功能，但是其逻辑不够高效，导致文件备份缓慢。</p>
<p>因此，在这里推荐一款<strong>轻量、开源、高效</strong>的<strong>文件备份工具</strong>，项目名称是 <strong>FreeFileSync</strong>，希望可以帮到大家。</p>
<p><em>演示系统：Windows 11。</em></p>
<hr />
<h1>2. 操作方法</h1>
<h2>2.1 安装</h2>
<p>前往 <a href="https://example.com">FreeFileSync 官方网站</a>下载并安装软件。</p>
<p>https://freefilesync.org/</p>
<h2>2.2 使用方法</h2>
<ol>
<li>打开软件，在上方选择对应的文件夹路径。左侧是原文件路径，右侧是备份路径。</li>
</ol>
<p><img src="1.webp" alt="选择路径示意" /></p>
<ol>
<li>点击上方 <strong>蓝色齿轮</strong> 图标，设置文件比对逻辑。此处一般默认即可。</li>
</ol>
<p><img src="2.webp" alt="比对逻辑" /></p>
<ol>
<li>点击上方 <strong>绿色齿轮</strong> 图标，设置备份模式。</li>
</ol>
<p><img src="3.webp" alt="备份模式" /></p>
<p>下面对各种模式进行简单说明。</p>
<ul>
<li><code>双向</code>：左右路径中的文件相互更新，使两侧文件一致。可用在多人协作中。</li>
<li><code>镜像</code>：一般的文件备份模式。左侧路径中添加、修改、删除的文件，右侧会同样处理。也就是说，备份文件夹中的文件和原文件完全一致。</li>
<li><code>更新</code>：可以理解为，只在右侧添加、修改文件，而不删除文件的<code>镜像</code>模式。</li>
<li><code>自定义</code>：此模式下，可以设置你自己需要的文件同步逻辑。</li>
</ul>
<ol>
<li>先后点击 <strong>比较</strong> 和 <strong>同步</strong> 按钮，完成文件同步。</li>
</ol>
<hr />
<h1>3. 总结</h1>
<p>无论是开发人员备份项目文件，还是普通用户同步本地与移动硬盘数据，FreeFileSync 都能通过精准的文件差异比对，减少冗余数据传输，提升备份效率，是一款值得长期收藏使用的实用工具。</p>
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="信息学"/>
  </entry>
  <entry>
    <title>DIY｜电机改音响</title>
    <link href="https://luq-blog.xyz/posts/2026-05-02-motor-audio/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-05-02-motor-audio/</id>
    <published>2026-05-02T00:00:00.000Z</published>
    <updated>2026-06-12T00:00:00.000Z</updated>
    <summary>关于使用功放板让电机发声的原理及教程。</summary>
    <content type="html"><![CDATA[<h1>1. 前言</h1>
<p>相信你一定在视频平台上看过电机或机械硬盘改成音响的视频吧，这篇文章就来讲讲<strong>电机发声</strong>的原理，并教你实战完成两种 <strong>DIY 项目</strong>。</p>
<p>先来看看成果演示视频。</p>
<blockquote>
<p><strong>演示视频</strong>：</p>
<p>&lt;iframe width="100%" height="468" src="//player.bilibili.com/player.html?isOutside=true&amp;aid=116483103391816&amp;bvid=BV1nL9yBbEiB&amp;cid=37920899208&amp;p=1" scrolling="no" autoplay=0 frameborder="no" framespacing="0" allowfullscreen="true"&gt;&lt;/iframe&gt;</p>
</blockquote>
<hr />
<h1>2. 技术原理</h1>
<p><s>众所周知，直流电机不是喇叭（</s></p>
<p>但是，其结构决定了它完全可以当作振动发声单元使用。</p>
<h2>2.1 发声本质</h2>
<ul>
<li>喇叭的原理是：电信号→线圈受力→振动空气→声音。</li>
<li>电机内部也有<strong>线圈</strong>和<strong>磁铁</strong>。因此，如果给它输入音频交变信号，线圈就会不断受力、快速振动，带动整个电机外壳震动，推动空气就能发出声音。</li>
</ul>
<h2>2.2 功放板作用</h2>
<p>电脑音频输出电压太小，推力不够，带不动电机振动。所以需要使用功率放大电路板，把微弱的音频信号放大功率，足够驱动电机大幅度振动，声音才够大、够清晰。</p>
<p>理论部分结束，开始实战！</p>
<hr />
<h1>3. 操作方法</h1>
<h2>3.1 方案一：普通直流电机</h2>
<h3>3.1.1 材料准备</h3>
<ol>
<li>一个或两个<strong>直流电机</strong>，<em>额定电压不超过12V</em>。</li>
<li>一块<strong>功放板</strong>，<em>演示型号：PAM8610</em>。</li>
</ol>
<p><img src="1.webp" alt="功放板展示" /></p>
<ol>
<li>一根 <strong>DC5V圆头</strong> 接口的电源线。</li>
<li>一根 <strong>双向3.5mm AUX</strong> 音频线。</li>
<li>焊接工具（可选）</li>
</ol>
<h3>3.1.2 接线方法</h3>
<p>观察功放板，其标识与功能的对应关系如下。</p>
<table>
<thead>
<tr>
<th>电路板标识</th>
<th>对应功能</th>
</tr>
</thead>
<tbody>
<tr>
<td>Power on</td>
<td>电源</td>
</tr>
<tr>
<td>Audio in</td>
<td>音频输入</td>
</tr>
<tr>
<td>OUTR+</td>
<td>右声道+</td>
</tr>
<tr>
<td>OUTR-</td>
<td>右声道-</td>
</tr>
<tr>
<td>OUTL+</td>
<td>左声道+</td>
</tr>
<tr>
<td>OUTL-</td>
<td>左声道-</td>
</tr>
</tbody>
</table>
<p>将直流电机的两个引脚分别接入其中一个声道的两个接口，接入电源线和音频线，把音频线接入到电脑或手机上。必要时可以通过焊接来固定。</p>
<p><img src="2.webp" alt="电机音响展示" /></p>
<p>设置一下音频输出设备，然后播放一首音乐。电机音响就开始工作了。</p>
<h2>3.2 方案二：机械硬盘的三相直流电机</h2>
<h3>3.2.1 材料准备</h3>
<ol>
<li>一块<strong>机械硬盘</strong>。</li>
<li>同方案一2-5。</li>
<li><strong>万用表</strong>，<em>用于后面测量电阻值</em>。</li>
</ol>
<h3>3.2.2 拆卸</h3>
<p>用螺丝刀把硬盘的主控板拆下。可以选择拆除后盖和整个磁头。<em>此处演示盘片电机发声的方法。</em></p>
<p><img src="3.webp" alt="硬盘展示" /></p>
<h3>3.2.3 原理及分辨</h3>
<p>盘片电机一般有3线和4线。其原理图如下：</p>
<p><img src="4.webp" alt="电机原理" /></p>
<p>可以发现，演示用的盘片电机是4线。由原理图可知，将公共端和其中一根相线分别连接同一声道的两个接口，即可实现与普通直流电机相同的效果。所以要先确定应当连接哪两个引脚才能正常发声。</p>
<p>在四线制三相直流电机中，公共端与相线的电阻值基本相等，相线之间的电阻值基本相等且大于前者。通过此方法找出公共端和一根相线，并连接导线。</p>
<p><img src="5.webp" alt="接线展示" /></p>
<h3>3.2.4 接线方法</h3>
<p>将公共端和其中一根相线分别连接功放板同一声道的两个接口，接入电源线和音频线，即可播放声音。</p>
<p><img src="6.webp" alt="整体展示" /></p>
<hr />
<h1>4. 总结</h1>
<p>整个过程无需专业知识，材料易得、操作简单，既掌握了电机发声的核心原理，又能做出专属的简易音响，既能体验动手的乐趣，也能实现闲置硬件的二次利用，是一款值得尝试的趣味DIY项目。</p>
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="信息学"/>
  </entry>
  <entry>
    <title>GitHub｜加速方法</title>
    <link href="https://luq-blog.xyz/posts/2026-04-26-github-accelerate/" rel="alternate" type="text/html"/>
    <id>https://luq-blog.xyz/posts/2026-04-26-github-accelerate/</id>
    <published>2026-04-12T00:00:00.000Z</published>
    <updated>2026-06-20T00:00:00.000Z</updated>
    <summary>分享国内安全、合规、仅用于开发用途的GitHub加速方案。</summary>
    <content type="html"><![CDATA[<h1>1. 前言</h1>
<p>日常写代码、逛开源仓库、拉取项目代码，离不开GitHub，但国内原生访问经常超时失败。</p>
<p>此处推荐一个适合<strong>长期自用、干净纯粹、只针对开发场景</strong>的GitHub加速方案。此方案不属于全局代理翻墙，只优化GitHub域名网络，完全用于日常编程学习与开源交流，希望可以帮到大家。</p>
<hr />
<h1>2. 操作方法</h1>
<p>在进行接下来的操作之前，请务必关闭所有第三方安全防护软件，以避免在过程中出现一些错误。</p>
<h2>2.1 安装 Watt Toolkit</h2>
<p>Watt Toolkit 是一款轻量、免费的网络优化工具，最初用于 Steam 游戏加速，后续新增了GitHub加速模块。具体步骤如下：</p>
<ol>
<li>前往 <a href="https://steampp.net/download">Watt Toolkit 官方网站</a> 下载最新版本。</li>
</ol>
<p><strong>注</strong>：有时此网址无法正常访问，可使用微软商店安装。</p>
<ol>
<li>安装完成后打开软件，在左侧菜单栏找到 <code>网络加速</code> 选项。</li>
</ol>
<p><img src="1.webp" alt="网络加速选项示意" /></p>
<ol>
<li>
<p>在加速列表中，勾选 <code>GitHub</code> 及相关附属加速项。</p>
</li>
<li>
<p>点击 <code>一键加速</code>，等待提示加速成功后，即可正常访问GitHub，进行后续操作。</p>
</li>
</ol>
<p><img src="2.webp" alt="一键加速选项示意" /></p>
<h2>2.2 安装 DevSidecar</h2>
<p>DevSidecar是专为国内开发者量身打造的开发环境加速工具，无广告、轻量且开源。</p>
<p>::github{repo="docmirror/dev-sidecar"}</p>
<ol>
<li>在 Watt Toolkit 加速打开的情况下，前往仓库，在右侧 <code>release</code> 下载并安装。</li>
</ol>
<p><img src="3.webp" alt="release下载示意" /></p>
<p><strong>注</strong>：Windows 用户请选择 <code>DevSidecar-x.x.x-windows-universal.exe</code>，其他系统见仓库下方技术文档。</p>
<ol>
<li>安装完成后打开软件，第一次打开会提示安装证书，根据提示操作即可。</li>
</ol>
<p><img src="4.webp" alt="DevSidecar主页示意" /></p>
<h2>2.3 配置油猴插件（可选）</h2>
<p>DevSidecar内置油猴脚本，但是更新可能不及时且不够稳定，此处以Edge浏览器为例，演示手动安装GitHub加速脚本的方法。</p>
<ol>
<li>
<p>在 <code>扩展</code> - <code>获取Microsoft Edge扩展</code> 中搜索 <code>篡改猴</code> 并安装。</p>
</li>
<li>
<p>前往 <a href="https://greasyfork.org/zh-CN/scripts/412245-github-enhancement-high-speed-download">GitHub高速下载脚本安装链接</a>，点击 <code>安装脚本</code> 后会跳转安装页面，点击 <code>安装</code>。</p>
</li>
<li>
<p>现在还需要进行一些配置设置。在 <code>管理扩展</code> - <code>篡改猴</code> - <code>详细信息</code> 中，勾选 <code>允许用户脚本</code>。</p>
</li>
</ol>
<p><img src="5.webp" alt="管理扩展选项示意" /></p>
<ol>
<li>在 <code>扩展</code> - <code>篡改猴</code> - <code>管理面板</code> - <code>设置</code> 中，将 <code>通用</code> - <code>配置模式</code> 修改为 <code>高级</code>，将 <code>安全</code> - <code>修改内容安全策略（CSP）头信息</code> 修改为 <code>全部移除</code>，点击 <code>保存</code>。</li>
</ol>
<p>此时刷新GitHub页面，下载链接就会提供多种加速渠道了。通过 <code>git clone</code> 命令即可加速下载项目代码。</p>
<p><img src="6.webp" alt="下载选项示意" /></p>
<hr />
<h1>3. 总结</h1>
<p>对于国内开发者而言，这些工具都是安全、合规、轻量的GitHub加速选择，能有效解决GitHub访问卡顿、加载失败、Clone缓慢等问题。非常适合需要日常使用GitHub学习、开发、协作的开发者。</p>
<p>如果在使用过程中遇到加速失败、无法生效等问题，可前往两款工具的官方仓库查看解决方案，或留言交流。</p>
<p>:::important[重要合规声明]
本文介绍的均为开发专用加速工具，不具备通用翻墙能力；请自觉遵守《中华人民共和国网络安全法》及相关法律法规，合理使用工具，不利用工具从事任何违规、违法活动；本文仅做技术分享与学习交流，不鼓励、不引导任何违规网络行为，若工具后续功能调整，需以官方说明为准。
:::</p>
]]></content>
    <author><name>Luquiescene</name></author>
    <category term="信息学"/>
  </entry>
</feed>
