查看全课大纲(第 41 / 51 节)
  1. 1.为什么人人都该学点编程?
  2. 2.计算机与程序设计:你的电脑到底在“想”什么?
  3. 3.Python 这门语言,到底有什么魔力?
  4. 4.Python 开发环境配置:如何拥有你的第一个“编程工具箱”?
  5. 5.如何安装一个更强大的代码编辑器?
  6. 6.汇率兑换1.0:你的第一个“钱生钱”小程序
  7. 7.你的第一行代码,藏着哪些秘密?
  8. 8.汇率兑换2.0(上):让程序看懂“CNY”和“USD”
  9. 9.汇率兑换程序,如何让它“聪明”地判断币种?
  10. 10.汇率兑换程序能一直运行,直到我喊停吗?
  11. 11.汇率兑换4.0:如何把“算钱”功能打包成积木?
  12. 12.汇率兑换5.0:给你的程序穿上“结构化”的西装
  13. 13.怎么用 Python 画一个会动的五角星?
  14. 14.如何让程序自动画出不同大小的五角星?
  15. 15.函数自己调用自己,会发生什么?
  16. 16.用递归画出神奇的分形树
  17. 17.Python 怎么计算你一天躺着不动能消耗多少热量?
  18. 18.如何让程序“活”起来,持续为你服务?
  19. 19.如何让 BMR 计算器更“聪明”?
  20. 20.当程序遇到“不按套路出牌”的用户怎么办?
  21. 21.怎么用 Python 模拟一个“滚雪球”式的存钱计划?
  22. 22.如何用列表记录每一周的存款?
  23. 23.如何让程序自己数数?—— 用 for 循环解放你的双手
  24. 24.52周存钱挑战4.0:你的存款计算器能有多灵活?
  25. 25.52周存钱挑战:怎么根据日期自动查存款?
  26. 26.Python 怎么判断一年中的第几天?
  27. 27.列表 vs 元组:哪个更适合存储月份天数?
  28. 28.用集合给月份分个队,换个思路算天数
  29. 29.把月份和天数“锁”在一起:用字典给数据配个对
  30. 30.Python 怎么判断密码强弱?
  31. 31.Python 怎么给密码设置“防沉迷”系统?
  32. 32.Python 怎么把密码保存到文件里?
  33. 33.Python 怎么读取文件里的密码?
  34. 34.Python 怎么把密码检查工具“打包”起来?
  35. 35.Python 怎么把文件操作也“打包”起来?
  36. 36.计算机怎么“扔骰子”?——用Python模拟随机事件
  37. 37.如何用 Python 模拟掷两个骰子?
  38. 38.怎么用 Python 把骰子轨迹画出来?
  39. 39.骰子点数分布长啥样?用直方图一眼看穿!
  40. 40.用科学计算库让骰子程序“起飞”
  41. 41.空气质量指数怎么算?手把手教你写一个AQI计算器
  42. 42.空气质量指数计算:从零开始,让程序看懂空气
  43. 43.如何读取空气质量数据文件并找出空气质量最好的五个监测点?
  44. 44.JSON 数据怎么变成 Excel 表格?—— 让数据换个“马甲”
  45. 45.Python 怎么让程序自己判断文件类型?
  46. 46.如何让程序自己上网查空气质量?
  47. 47.用美味的汤来解析网页:Beautiful Soup 入门
  48. 48.如何让爬虫自动抓取全国所有城市的空气质量?
  49. 49.爬到的数据怎么存下来?—— 把全国空气质量数据保存为CSV文件
  50. 50.用 pandas 玩转空气质量数据:找出全国空气最好和最差的城市
  51. 51.空气质量数据怎么“洗”干净,还能画成图?

空气质量指数怎么算?手把手教你写一个AQI计算器

约 18 分钟

小象实战讲义 · Python 入门:8 个小项目边玩边学

欢迎来到我们《Python 入门》课程的最后一个项目!经过前面八个项目的历练,你已经从完全不会写代码,到现在能做出汇率换算、存钱计划、密码评分等实用工具。今天我们要挑战一个跟生活特别相关的项目——空气质量指数(AQI)计算器。

想象一下:你早上出门前,想知道今天的空气质量怎么样,是适合户外运动还是最好待在室内?通过今天的学习,你将能自己写一个程序,输入PM2.5、一氧化碳等污染物的浓度值,就能计算出对应的AQI指数,告诉你空气质量到底如何。

更重要的是,这个项目会把你之前学过的所有知识都用上,还会加入一些新的技巧,让你感受到「学以致用」的成就感。

💡 核心导读

本节你将学到:

  • AQI的计算原理:空气质量指数是怎么算出来的?
  • 函数的分层设计:如何把一个复杂问题拆成几个简单函数
  • 列表与最大值:如何从多个污染物指数中找出最差的那个
  • 线性缩放:数学中的「按比例换算」在编程中怎么实现

空气质量指数:一个数字背后的科学

空气质量指数(AQI)是一个用来定量描述空气质量状况的数字。你可能在天气预报里见过它,比如「今日AQI 85,空气质量良」。但这个数字是怎么来的呢?

其实,AQI是由多种污染物的分指数计算出来的。空气中常见的污染物有:

  • PM2.5(细颗粒物)
  • PM10(可吸入颗粒物)
  • 二氧化硫(SO₂)
  • 二氧化氮(NO₂)
  • 一氧化碳(CO)
  • 臭氧(O₃)

每种污染物都有自己的浓度标准。环保部门会监测这些污染物的浓度,然后通过一个公式计算出每种污染物的「分指数」(IAQI),最后取所有分指数中的最大值作为最终的AQI。

为什么取最大值?因为空气质量是由「最差」的那个污染物决定的!就像木桶的短板效应一样。

查表计算:从浓度到指数

我们来看一个具体的例子。假设监测到PM2.5的浓度是55 µg/m³(微克每立方米)。

根据国家标准,PM2.5的浓度分级是这样的:

浓度范围(µg/m³)对应的AQI范围
0 – 350 – 50
35 – 7550 – 100
75 – 115100 – 150
…(后面还有更多分级)

55落在35-75这个范围里,所以我们需要把浓度值55「映射」到AQI的50-100这个范围。

这其实是一个线性缩放的过程:把浓度范围[35, 75]按比例缩放到指数范围[50, 100]。

计算公式是这样的:

IAQI = (IAQI_高 - IAQI_低) × (浓度 - 浓度_低) ÷ (浓度_高 - 浓度_低) + IAQI_低

代入我们的数值:

IAQI = (100 - 50) × (55 - 35) ÷ (75 - 35) + 50
     = 50 × 20 ÷ 40 + 50
     = 25 + 50
     = 75

所以PM2.5浓度为55时,它的分指数是75。

本节新增了什么? 我们第一次接触「查表计算」和「线性缩放」的概念,这是很多实际应用(比如成绩换算、温度转换)的基础思路。


搭建程序框架:先想清楚再动手

写复杂程序时,一个好习惯是先搭框架,再填细节。就像盖房子先画设计图一样。

我们的AQI计算器需要做这几件事:

  1. 让用户输入两种污染物的浓度值
  2. 分别计算每种污染物的分指数
  3. 取两个分指数中的最大值作为最终AQI
  4. 输出结果
# AQI计算器 v1.0 - 框架版
# 功能:计算空气质量指数

def cal_pm_iaqi(pm_val):
    """计算PM2.5的分指数"""
    # 这里先留空,后面实现
    pass

def cal_co_iaqi(co_val):
    """计算一氧化碳的分指数"""
    # 这里先留空,后面实现
    pass

def cal_aqi(param_list):
    """计算最终的AQI"""
    pm_val = param_list[0]  # 第一个值是PM2.5
    co_val = param_list[1]  # 第二个值是一氧化碳
    
    # 计算各自的分指数
    pm_iaqi = cal_pm_iaqi(pm_val)
    co_iaqi = cal_co_iaqi(co_val)
    
    # 放到列表里,取最大值
    iaqi_list = []
    iaqi_list.append(pm_iaqi)
    iaqi_list.append(co_iaqi)
    
    aqi = max(iaqi_list)  # 取最大值
    return aqi

# 主程序开始
print('请输入以下信息,用空格分割')
# 原程序用 input() 让用户输入,这里用固定值代替,你可以改成自己的值试
input_str = '55 2'  # PM2.5=55, 一氧化碳=2
print(f'(1)PM2.5 (2)CO: {input_str}')

# 分割字符串,转换成数字
str_list = input_str.split(' ')
pm_val = float(str_list[0])
co_val = float(str_list[1])

# 放到列表里传给计算函数
param_list = []
param_list.append(pm_val)
param_list.append(co_val)

# 调用AQI计算函数
aqi_val = cal_aqi(param_list)

print('空气质量指数为:{}'.format(aqi_val))

运行这个框架程序,虽然还不能真正计算(因为分指数函数还没实现),但你能看到程序的流程已经清晰了。

小技巧:pass 是Python的一个特殊语句,意思是「这里先占个位置,什么都不做」。在搭框架时特别有用。


实现线性缩放函数

刚才我们提到,计算分指数本质上是做线性缩放。这个功能很通用,我们可以把它单独写成一个函数。

# 线性缩放函数
def cal_linear(iaqi_lo, iaqi_hi, bp_lo, bp_hi, cp):
    """
    线性缩放函数
    参数说明:
    iaqi_lo: 目标范围下限(AQI低值)
    iaqi_hi: 目标范围上限(AQI高值)
    bp_lo:  原始范围下限(浓度低值)
    bp_hi:  原始范围上限(浓度高值)
    cp:     当前浓度值
    """
    # 公式:IAQI = (IAQI高 - IAQI低) × (浓度 - 浓度低) ÷ (浓度高 - 浓度低) + IAQI低
    iaqi = (iaqi_hi - iaqi_lo) * (cp - bp_lo) / (bp_hi - bp_lo) + iaqi_lo
    return iaqi

# 测试一下:PM2.5浓度55,应该在35-75映射到50-100
test_result = cal_linear(50, 100, 35, 75, 55)
print(f'PM2.5浓度55对应的分指数是:{test_result}')
PM2.5浓度55对应的分指数是:75.0
PM2.5浓度55对应的分指数是:75.0

看,我们得到了和手工计算一样的结果!这个函数现在可以复用了,任何需要线性缩放的场景都能用它。

生活化类比:这就像把考试成绩从「百分制」换算到「150分制」。如果100分制的80分相当于150分制的120分,那么85分应该换算到多少?用我们的cal_linear函数就能轻松算出来!


完善污染物计算函数

有了线性缩放函数,现在我们可以完善PM2.5和一氧化碳的计算函数了。

根据国家标准,PM2.5的分级标准是:

  • 0-35 µg/m³ → AQI 0-50
  • 35-75 µg/m³ → AQI 50-100
  • 75-115 µg/m³ → AQI 100-150
  • …(实际还有更多级,我们先实现前三级)

一氧化碳(CO)的标准是:

  • 0-3 mg/m³ → AQI 0-50
  • 3-5 mg/m³ → AQI 50-100
  • …(同样先实现前两级)
# 完整的AQI计算器 v1.0
def cal_linear(iaqi_lo, iaqi_hi, bp_lo, bp_hi, cp):
    """线性缩放函数"""
    iaqi = (iaqi_hi - iaqi_lo) * (cp - bp_lo) / (bp_hi - bp_lo) + iaqi_lo
    return iaqi

def cal_pm_iaqi(pm_val):
    """计算PM2.5的分指数"""
    if 0 <= pm_val < 36:  # 注意:35是闭区间,36是开区间
        # 0-35 映射到 0-50
        iaqi = cal_linear(0, 50, 0, 35, pm_val)
    elif 36 <= pm_val < 76:
        # 35-75 映射到 50-100
        iaqi = cal_linear(50, 100, 35, 75, pm_val)
    elif 76 <= pm_val < 116:
        # 75-115 映射到 100-150
        iaqi = cal_linear(100, 150, 75, 115, pm_val)
    else:
        # 超过115的情况暂时不处理
        print(f'PM2.5浓度{pm_val}超出当前处理范围')
        iaqi = 0
    return iaqi

def cal_co_iaqi(co_val):
    """计算一氧化碳的分指数"""
    if 0 <= co_val < 3:
        # 0-3 映射到 0-50
        iaqi = cal_linear(0, 50, 0, 3, co_val)
    elif 3 <= co_val < 5:
        # 3-5 映射到 50-100
        iaqi = cal_linear(50, 100, 2, 4, co_val)  # 注意:标准是2-4,不是3-5
    else:
        # 超过5的情况暂时不处理
        print(f'一氧化碳浓度{co_val}超出当前处理范围')
        iaqi = 0
    return iaqi

def cal_aqi(param_list):
    """计算最终的AQI"""
    pm_val = param_list[0]
    co_val = param_list[1]
    
    pm_iaqi = cal_pm_iaqi(pm_val)
    co_iaqi = cal_co_iaqi(co_val)
    
    print(f'PM2.5分指数:{pm_iaqi:.1f}')
    print(f'一氧化碳分指数:{co_iaqi:.1f}')
    
    iaqi_list = []
    iaqi_list.append(pm_iaqi)
    iaqi_list.append(co_iaqi)
    
    aqi = max(iaqi_list)
    return aqi

# 主程序
print('=== AQI计算器 v1.0 ===')
print('请输入以下信息,用空格分割')

# 测试数据1:PM2.5=55, 一氧化碳=2
input_str = '55 2'
print(f'(1)PM2.5 (2)CO: {input_str}')

str_list = input_str.split(' ')
pm_val = float(str_list[0])
co_val = float(str_list[1])

param_list = [pm_val, co_val]  # 更简洁的列表创建方式

aqi_val = cal_aqi(param_list)
print('=' * 30)
print('空气质量指数为:{:.1f}'.format(aqi_val))

# 再测试一组数据
print('\n' + '=' * 30)
print('测试另一组数据:PM2.5=120, 一氧化碳=1')
test_aqi = cal_aqi([120, 1])
print('空气质量指数为:{:.1f}'.format(test_aqi))
=== AQI计算器 v1.0 ===
请输入以下信息,用空格分割
(1)PM2.5 (2)CO: 55 2
PM2.5分指数:75.0
一氧化碳分指数:33.3
==============================
空气质量指数为:75.0

==============================
测试另一组数据:PM2.5=120, 一氧化碳=1
PM2.5浓度120超出当前处理范围
PM2.5分指数:0.0
一氧化碳分指数:16.7
空气质量指数为:16.7
=== AQI计算器 v1.0 ===
请输入以下信息,用空格分割
(1)PM2.5 (2)CO: 55 2
PM2.5分指数:75.0
一氧化碳分指数:33.3
==============================
空气质量指数为:75.0

==============================
测试另一组数据:PM2.5=120, 一氧化碳=1
PM2.5浓度120.0超出当前处理范围
PM2.5分指数:0.0
一氧化碳分指数:16.7
空气质量指数为:16.7

程序解读:

  1. 第一组数据(PM2.5=55, CO=2):PM2.5分指数75,CO分指数33.3,取最大值75
  2. 第二组数据(PM2.5=120):因为120超出了我们目前处理的范围(0-115),所以返回0,最终AQI由CO的16.7决定

注意:实际应用中需要处理所有浓度范围,我们这里为了简化只实现了前几级。你可以看到,当数据超出范围时,程序会给出提示。


函数设计的思想:分层与复用

回顾一下我们的程序结构:

cal_aqi()          ← 顶层函数:协调整个计算流程
├── cal_pm_iaqi()  ← 中层函数:处理PM2.5的具体计算
│   └── cal_linear() ← 底层函数:通用的线性缩放
└── cal_co_iaqi()  ← 中层函数:处理一氧化碳的具体计算
    └── cal_linear() ← 复用同一个底层函数

这种「分层设计」的思想在编程中非常重要:

  • 底层函数(如cal_linear)做最通用、最基础的工作
  • 中层函数(如cal_pm_iaqi)处理特定领域的逻辑
  • 顶层函数(如cal_aqi)负责协调和整合

好处是什么?

  1. 易于维护:如果要修改线性缩放的算法,只需要改cal_linear一个地方
  2. 易于扩展:要增加新的污染物(如二氧化硫),只需要写一个新的cal_so2_iaqi函数,然后在cal_aqi里调用它
  3. 代码复用:cal_linear可以被所有污染物计算函数共用

真实工作中的应用:在软件开发中,这种分层思想无处不在。比如一个电商网站,底层有「计算运费」的函数,中层有「处理订单」的函数,顶层有「完成购物车结算」的函数。


📝 动手练一练

  1. 理解线性缩放
    如果考试成绩从「百分制」换算到「150分制」,且已知100分制的80分对应150分制的120分。请用cal_linear函数计算:100分制的92分应该换算到150分制的多少分?

  2. 扩展污染物类型
    我们的程序目前只处理PM2.5和一氧化碳。请尝试添加对PM10的支持。PM10的标准是:

    • 0-50 µg/m³ → AQI 0-50
    • 50-150 µg/m³ → AQI 50-100

    你需要:

    • 编写cal_pm10_iaqi(pm10_val)函数
    • 修改cal_aqi函数,让它能接收三个值(PM2.5, CO, PM10)
    • 在最终计算时考虑PM10的分指数
  3. 优化用户输入
    现在的程序要求用户用空格分割两个数字,如果用户输错了(比如多输了一个空格,或者输入了非数字字符),程序会崩溃。你能想到什么办法让程序更健壮吗?

👉 点击查看第1题参考答案
def cal_linear(iaqi_lo, iaqi_hi, bp_lo, bp_hi, cp):
    """线性缩放函数"""
    iaqi = (iaqi_hi - iaqi_lo) * (cp - bp_lo) / (bp_hi - bp_lo) + iaqi_lo
    return iaqi

# 百分制80分对应150分制120分
# 所以原始范围是[0, 100],目标范围是[0, 150]
# 注意:80分是已知对应点,但不是范围边界
# 我们需要用两个点来确定线性关系

# 方法:用0分和80分这两个已知对应点
# 0分 → 0分,80分 → 120分
# 所以当cp=92时:
result = cal_linear(0, 120, 0, 80, 92)
print(f'100分制的92分对应150分制的:{result:.1f}分')

# 验证:比例是120/80=1.5,92×1.5=138,结果一致
100分制的92分对应150分制的:138.0分
100分制的92分对应150分制的:138.0分

思路:线性缩放需要知道两个范围。已知「80分→120分」和「0分→0分」,所以原始范围是[0, 80],目标范围是[0, 120]。用这个比例计算92分的对应值。

👉 点击查看第2题参考答案
def cal_linear(iaqi_lo, iaqi_hi, bp_lo, bp_hi, cp):
    """线性缩放函数"""
    iaqi = (iaqi_hi - iaqi_lo) * (cp - bp_lo) / (bp_hi - bp_lo) + iaqi_lo
    return iaqi

def cal_pm10_iaqi(pm10_val):
    """计算PM10的分指数"""
    if 0 <= pm10_val < 51:  # 0-50
        iaqi = cal_linear(0, 50, 0, 50, pm10_val)
    elif 51 <= pm10_val < 151:  # 50-150
        iaqi = cal_linear(50, 100, 50, 150, pm10_val)
    else:
        print(f'PM10浓度{pm10_val}超出当前处理范围')
        iaqi = 0
    return iaqi

# 测试
pm10_test = 85  # PM10浓度85
pm10_iaqi = cal_pm10_iaqi(pm10_test)
print(f'PM10浓度{pm10_test}对应的分指数是:{pm10_iaqi:.1f}')

# 如果要集成到主程序,需要:
# 1. 修改cal_aqi函数,接收三个参数
# 2. 在iaqi_list中添加pm10_iaqi
# 3. 修改用户输入提示和参数列表
PM10浓度85对应的分指数是:67.5
PM10浓度85对应的分指数是:67.5

关键点:PM10浓度85落在50-150范围,按比例缩放到50-100,计算结果是67.5。

👉 点击查看第3题思路提示

思路方向(不写完整代码,只给思路):

  1. 处理多余空格:可以用strip()去掉字符串两端的空格
  2. 处理多个空格:可以用split()不指定分隔符,它会自动处理连续的空格
  3. 检查输入数量:判断分割后的列表长度是否符合预期
  4. 处理非数字输入:用try...except捕获转换错误

示例代码片段:

input_str = '55   2  '  # 有多余空格
str_list = input_str.strip().split()  # 不指定分隔符,自动处理连续空格

if len(str_list) != 2:
    print('请输入两个数值,用空格分割')
else:
    try:
        pm_val = float(str_list[0])
        co_val = float(str_list[1])
        # 继续处理...
    except ValueError:
        print('请输入有效的数字')

进阶思考:如果用户输入「55,2」用逗号分割怎么办?如果输入「五十五 二」中文数字怎么办?在实际项目中,用户输入验证是一个重要课题。


本章小结

恭喜!你已经完成了AQI计算器的第一个版本。虽然它目前只处理两种污染物,但核心的计算逻辑已经完整了。

今天你学到了:

  • ✅ AQI的计算原理:由多种污染物的分指数取最大值得到
  • ✅ 线性缩放:把数值从一个范围按比例转换到另一个范围
  • ✅ 函数分层设计:底层通用函数 + 中层专用函数 + 顶层协调函数
  • ✅ 列表与最大值:用max()函数找出列表中的最大值

📋 行动清单

学完立刻可以做的:

  • 运行今天的完整代码,尝试不同的PM2.5和CO浓度值
  • 修改程序,添加对二氧化硫(SO₂)的支持(标准:0-150 µg/m³ → AQI 0-50)
  • 思考:如果要处理全国所有城市的AQI数据,程序结构可以怎么优化?

在接下来的几节中,我们将:

  1. 从文件读取真实的污染物数据(JSON格式)
  2. 把计算结果保存到文件
  3. 从网络获取实时的空气质量数据
  4. 用更强大的工具(pandas)分析全国城市的AQI排名

你会发现,今天搭建的这个「计算引擎」是整个项目的基础。就像汽车发动机一样,后面的所有功能(读取数据、保存结果、网络获取)都是在这个引擎上添加的「配件」。

准备好了吗?下一节,我们将学习如何处理JSON数据文件,让程序能读取预先准备好的污染物数据!

—— 小象教研组

配套资料 · 直接下载

课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。

  • 第9章代码包:空气质量指数计算(含全部版本 10 个 .py)
    ZIP · 64KB
    下载
  • 数据集:aqi.csv(各城市 AQI 长表)
    772B
    下载
  • 数据集:beijing_aqi.json(北京监测点)
    3KB
    下载
  • 数据集:bottom10_aqi.csv(空气质量最差 10 城)
    412B
    下载
  • 数据集:china_city_aqi.csv(全国城市 AQI)
    13KB
    下载
  • 数据集:shanghai_aqi.json(上海监测点)
    3KB
    下载
  • 数据集:top10_aqi.csv(空气质量最好 10 城)
    372B
    下载
  • 数据集:top5_aqi.json(空气质量最好 5 城)
    1007B
    下载
看图文没弄懂?展开看本节视频讲解(约 18 分钟)
📺 正在播放小象学院官方高清录播(支持倍速与清晰度调节)