AI编程(二十六):CSV数据分析 — 看懂一堆数据的全貌

作者:

在

平均值、最大值、分布规律——几百行数据,三秒钟说出它的故事

一、数据搬进来了,然后呢?

上一篇我们干了件大事:把CSV读进了程序,还能把结果写回去。恭喜,你已经是个合格的”数据搬运工”了。

但搬运工之后呢?想象这个场景:

你按上一篇的办法,把三个月的银行账单.csv读进了程序,屏幕上哗啦啦滚过几百行。然后——就没有然后了。你盯着几百行滚动的记录,脑子里还是一团浆糊:

  • 我这三个月总共花了多少钱?
  • 平均每天花多少?算完会不会吓一跳?
  • 最贵的一笔是什么?在哪儿栽的跟头?
  • 钱都烧在哪个类别上了?奶茶真的比打车贵吗?
  • 有没有规律?是不是每月15号发工资后就开始报复性消费?

你看,”读出来”只是开始,“看懂”才是目的。这就是今天的主题:CSV数据分析——用程序给一堆数据算总账、找规律,让它开口说人话。

听着高大上?别怕。今天的新知识只有一个,小到你五分钟就能学会:列表统计四件套。剩下的全是前25篇的老朋友。学完这篇,你就能对任何一份表格数据说出它的”全貌”——这在工作里可是个能拿得出手的本事。

二、新招就一个:列表统计四件套

先看一堆真实的数字。假设你把账单的金额列读了出来,装进一个列表:

每天支出 = [25, 18.5, 56, 12, 89.9, 33, 45.6, 78]

想”看懂”这堆数,你只需要问四个问题,Python就有现成的四个函数等着:

你想知道 怎么问 结果

|———|——–|——|

总共多少 sum(每天支出) 358.0
有多少笔 len(每天支出) 8
最大一笔 max(每天支出) 89.9
最小一笔 min(每天支出) 12

再加上一个自己算的平均值:sum(每天支出) / len(每天支出)。

就这些。真的就这些。sum、len、max、min——四个单词,Python自带,不用安装,不用import。它们对任何列表都好使:

成绩 = [85, 92, 78, 96, 67]
print(sum(成绩))      # 418  总分
print(len(成绩))      # 5    人数
print(max(成绩))      # 96   最高分
print(min(成绩))      # 67   最低分
print(sum(成绩) / len(成绩))  # 83.6  平均分

把这四件套记住,它是今天一切分析的发动机。所有花哨的”数据分析”,剥开外壳都是这四个函数加字典累加器——而字典累加器那招,你早在第十五篇单词统计器里就练熟了。

先跟AI热个身,熟悉一下今天的学习姿势(跟AI说需求 → 读懂 → 改进):

我有一个列表装着每天的支出金额,帮我写个Python程序,算出总额、笔数、平均值、最高、最低,并把结果打印得好看一点(带标签、保留两位小数)。

拿到代码先别急着跑,读一遍:它是不是在回答你问的那五个问题?哪里看不懂就追问。跑通之后再想”改进”——比如”平均值保留一位小数就行”。这个流程今天会走好几轮。

三、把CSV变成”能统计的数字”

好,四件套有了。但账单.csv里躺着的是字符串——上一篇埋的雷,今天要踩了。

import csv

金额列表 = []
with open("账单.csv", encoding="utf-8") as f:
    reader = csv.DictReader(f)
    for row in reader:
        金额列表.append(row["金额"])

print(sum(金额列表))  # 报错!

跑起来直接给你一巴掌:TypeError: unsupported operand type(s) for +: 'int' and 'str'。翻译成人话:”你让我把数字和文字加起来,我是程序不是神汉。”

原因上一篇讲过:CSV里读出来的所有东西都是字符串,”25″是文字不是数字。统计之前必须变身:

import csv

金额列表 = []
with open("账单.csv", encoding="utf-8") as f:
    reader = csv.DictReader(f)
    for row in reader:
        金额列表.append(float(row["金额"]))  # float() 变身成小数

print(f"总支出:{sum(金额列表):.2f} 元")
print(f"笔数:{len(金额列表)} 笔")
print(f"平均:{sum(金额列表) / len(金额列表):.2f} 元/笔")
print(f"最高:{max(金额列表):.2f} 元")
print(f"最低:{min(金额列表):.2f} 元")

float("25")把文字”25″变成数字25.0,int("25")变成整数25。金额这种带小数的,用float稳一点。:.2f是老朋友了——保留两位小数。

跑起来,五行统计一气呵成。几百行的账单,你的”全貌”已经出来了。但这还不够——总额只是开场白,真正的信息藏在”分组”里。

四、实战:给账单做一次”全身体检”

现在完整走一遍学习循环。跟AI说需求:

我有一个账单.csv,表头是”日期,项目,金额”,日期格式像”10-01″。帮我写个分析程序,输出:1)总支出和笔数;2)平均每天花多少;3)最贵的三笔支出;4)按项目分类的汇总(吃饭、打车、购物各花了多少);5)把分析结果存成一个分析报告.txt。

AI会给你一份挺长的代码。别慌,咱们一块块读懂它。完整的、能直接跑的版本在下面——你可以先造一份测试数据:

# 第0步:造一份演示用的账单.csv(跑一次就行,之后分析程序反复用)
import csv

明细 = [
    ["10-01", "午饭", 25], ["10-01", "打车", 18.5], ["10-02", "奶茶", 12],
    ["10-02", "晚饭", 45], ["10-03", "书", 56], ["10-03", "午饭", 22],
    ["10-04", "打车", 33], ["10-04", "购物", 199], ["10-05", "午饭", 28],
    ["10-05", "电影", 45.6], ["10-06", "晚饭", 68], ["10-06", "奶茶", 15],
    ["10-07", "打车", 22.5], ["10-07", "购物", 89.9], ["10-08", "午饭", 26],
    ["10-08", "晚饭", 52], ["10-09", "奶茶", 12], ["10-09", "打车", 31],
    ["10-10", "书", 78], ["10-10", "午饭", 24], ["10-11", "购物", 258],
    ["10-11", "晚饭", 41], ["10-12", "午饭", 27], ["10-12", "奶茶", 13],
]

with open("账单.csv", "w", encoding="utf-8", newline="") as f:
    writer = csv.writer(f)
    writer.writerow(["日期", "项目", "金额"])
    writer.writerows(明细)

print("账单.csv 已生成,共", len(明细), "笔")

然后是正主——账单分析器:

# 账单分析器:给账单做一次"全身体检"
import csv

# ---- 读数据:把CSV装进"字典列表" ----
明细 = []
with open("账单.csv", encoding="utf-8") as f:
    reader = csv.DictReader(f)
    for row in reader:
        明细.append({
            "日期": row["日期"],
            "项目": row["项目"],
            "金额": float(row["金额"]),   # 字符串变数字,这个坑上一节刚讲
        })

# ---- 体检项目1:总量、笔数、平均 ----
总额 = sum(item["金额"] for item in 明细)
笔数 = len(明细)
平均 = 总额 / 笔数

# ---- 体检项目2:最贵的三笔 ----
从贵到贱 = sorted(明细, key=lambda x: x["金额"], reverse=True)
前三 = 从贵到贱[:3]

# ---- 体检项目3:按项目分类汇总(字典累加器,第三次出场) ----
分类汇总 = {}
for item in 明细:
    项目 = item["项目"]
    分类汇总[项目] = 分类汇总.get(项目, 0) + item["金额"]

# ---- 打印报告 ----
print("=" * 30)
print("账单体检报告")
print("=" * 30)
print(f"总支出:{总额:.2f} 元(共 {笔数} 笔)")
print(f"平均每笔:{平均:.2f} 元")
print()
print("最贵的三笔:")
for i, item in enumerate(前三, 1):
    print(f"  第{i}名:{item['日期']} {item['项目']} {item['金额']:.2f} 元")
print()
print("分类汇总:")
for 项目, 小计 in 分类汇总.items():
    print(f"  {项目}:{小计:.2f} 元")

# ---- 存档:写进分析报告.txt ----
with open("分析报告.txt", "w", encoding="utf-8") as f:
    f.write(f"总支出 {总额:.2f} 元,共 {笔数} 笔,平均每笔 {平均:.2f} 元n")
    for 项目, 小计 in 分类汇总.items():
        f.write(f"{项目}: {小计:.2f}n")

print()
print("报告已存到 分析报告.txt")

运行输出(用上面那份演示数据):

==============================
账单体检报告
==============================
总支出:1241.50 元(共 24 笔)
平均每笔:51.73 元

最贵的三笔:
  第1名:10-11 购物 258.00 元
  第2名:10-04 购物 199.00 元
  第3名:10-07 购物 89.90 元

分类汇总:
  午饭:152.00 元
  打车:105.00 元
  奶茶:52.00 元
  晚饭:206.00 元
  书:134.00 元
  购物:546.90 元
  电影:45.60 元

报告已存到 分析报告.txt

逐块拆解,看它串起了多少老朋友:

  • 字典列表存明细——第十七篇成绩管理器的招牌结构,{"日期": ..., "项目": ..., "金额": ...}一行一条
  • sum + len + 平均——今天的新招,就这三下
  • sorted(key=lambda x: x[“金额”], reverse=True)——第十四篇用过的排序,reverse=True让它从大到小排;[:3]切片取前三。注意排序的是字典列表,所以key要写成”取每条的金额”,这正是lambda的用武之地
  • 字典累加器分类汇总.get(项目, 0) + item["金额"]——单词统计器、单位转换器、记账本里三次登场的”万能钥匙”,今天第四次。你发现规律了吗:统计类的问题,八成是它
  • float()转换——不转,上面整段代码全是错的
  • 写文件存结果——第九篇文件操作 + 上一篇csv.writer,双剑合璧

看到没?“数据分析”四个字听着吓人,拆开全是前25篇的招式。今天真正的新东西只有四件套。

五、找规律:把数据”分桶”看

总量、最值、分类汇总,都是”概括”。再往深一步看规律——数据在时间上怎么分布?是不是月初挥霍月底吃土?

思路还是分桶:按月份(或按星期)把钱归堆。日期长这样”10-01″,月份就是前两个字符——字符串切片row["日期"][:2],第25篇练习里预告过的招:

# 按月份分桶(接着上面的分析程序,在"分类汇总"后面加)
月份汇总 = {}
for item in 明细:
    月份 = item["日期"][:2]        # "10-01" → "10"
    月份汇总[月份] = 月份汇总.get(月份, 0) + item["金额"]

print("按月份分桶:")
for 月份, 小计 in 月份汇总.items():
    print(f"  {月份}月:{小计:.2f} 元")

想看”哪个月最烧钱”?四件套直接上:

最烧钱月 = max(月份汇总, key=月份汇总.get)
print(f"最烧钱的是 {最烧钱月} 月,花了 {月份汇总[最烧钱月]:.2f} 元")

max加key,意思是”比大小比的不是键(月份名),是值(钱数)”——sorted的key是老知识,max/min的key是同一个思路,AI给你的代码里经常见到,见到认识就行。

最后一招,把数字”画”出来。不用任何画图库,用*号堆个柱状图:

# 文本柱状图:每10元一个*
print("分类柱状图(每个*约10元):")
for 项目, 小计 in 分类汇总.items():
    长度 = int(小计 / 10)
    print(f"  {项目:<4} {'*' * 长度} {小计:.2f}")

输出:

分类柱状图(每个*约10元):
  午饭   *************** 152.00
  打车   ********** 105.00
  奶茶   ***** 52.00
  晚饭   ******************** 206.00
  书    ************* 134.00
  购物   ****************************************************** 546.90
  电影   **** 45.60

一眼看穿:购物那根柱子长得离谱。这就是”分布规律”的肉眼版——数据分析的终点不是数字,是”哦,原来钱都花在这儿了”。{项目:<4}是文字对齐的小花招(左对齐占4格),"*" * 长度是字符串乘法,第15篇见过。一晚上看完三个月的账,Excel里拖公式得拖半天。

六、三个必踩的坑(先打疫苗)

坑1:sum对字符串列表发脾气

第三节那个TypeError是今天最高频的坑,值得再敲一遍黑板:CSV里的一切都是字符串。统计、排序、比大小之前,先把数字列用float()(或int())变身。而且要转得干净——只转金额,别把日期也转了,float("10-01")会直接报错。

坑2:脏数据一颗老鼠屎坏一锅粥

真实世界的CSV可没演示数据那么乖:金额那列可能混着””(空)、”-“、”暂无”这种货色。float("")直接崩溃,程序当场去世,几百行数据白读。

上过第十篇错误处理的你,处理办法信手拈来:

金额列表 = []
跳过 = 0
for row in reader:
    try:
        金额列表.append(float(row["金额"]))
    except ValueError:
        跳过 += 1

print(f"有效 {len(金额列表)} 笔,跳过脏数据 {跳过} 行")

好的数据分析程序,一半代码在处理”脏数据”——这句话第25篇说过,今天你应该能体感到了。跟AI提需求时就把这句加上:”金额列可能有空值或非数字,帮我跳过并统计跳过了几行。”

坑3:除以零——空数据直接掀桌

sum(金额列表) / len(金额列表),如果文件是空的(或者全被跳过了),len是0,除以零(ZeroDivisionError)当场崩。加一行保险:

平均 = 总额 / len(金额列表) if 金额列表 else 0

“列表不为空才算平均,否则给0″——一行搞定。数据处理的第四个坑算是正式记在账上:处理真实数据前,先想好”数据为空/不干净怎么办”。

七、跟AI协作的正确姿势(数据分析特供)

数据分析是AI特别擅长的领域,但也是特别容易被糊弄的领域,三个要点:

  1. 把数据的样子喂给AI。”帮我分析CSV”等于没说;”表头是日期,项目,金额,日期长这样’10-01’,金额可能有空值,我想按项目汇总并找最贵的三笔”——AI代码一次到位。描述数据,不只是描述功能
  2. 读懂代码的”数据流”:数据从CSV进来 → 变成什么结构(列表?字典列表?)→ 中间被怎么加工(过滤?分组?)→ 结果存到哪。这条线看懂了,代码就在你脑子里了
  3. 拿结果反推验证。程序说总支出1260.50元,不信?Excel里拉个SUM对一下。分析结果对不上直觉时,错的通常是代码——尤其是忘了float()的时候,程序不报错,结果全是错的,这比崩溃更可怕

八、今日小练习

练习1:成绩分析报告

把第25篇练习造的成绩.csv(姓名,语文,数学,英语)拿出来,写程序输出:每科的平均分、最高分、最低分,以及每科的及格率(60分及以上算及格)。(提示:按列统计需要”先分科归堆”——字典科目 -> [分数列表],再对每个列表用四件套。及格率 = 及格人数 / len(列表) × 100。)

练习2:找出”消费规律”

给账单分析器加一个功能:按星期分桶(自己给每行数据加一列”星期”,造数据时顺手写上”周一”~”周日”),找出花钱最多的星期几,并输出”星期X最烧钱,花了xx元”。(提示:还是字典累加器+max(key=...),你已经是第四次用它了,闭着眼都能写。)

练习3:升级版柱状图

把第五节的柱状图改成”每5元一个*”,并且只画小计超过50元的类别,按小计从大到小排序输出。(提示:sorted(分类汇总.items(), key=lambda x: x[1], reverse=True)能同时拿到键值对并排序;”超过50元才画”就是个if。items()这个小助理,AI给的代码里天天出现,正好认识一下。)

写在后面:你已经会”数据分析”了

盘点一下你现在手里的牌:读任何CSV、清洗脏数据、总量统计、找最值、分类汇总、分组找规律、输出报告存档——一份表格数据从进门到”说出全貌”的完整流程,你已经走通了。

这套流程换个数据就能用:工资条、考勤表、店铺订单、跑步记录、微信账单导出……数据分析不是数据科学家的专利,它就是”字典列表+四件套+想清楚问题”。下一篇我们换一种数据格式,它比CSV更受程序欢迎——你在各种网站接口、配置文件里早就见过它了。

下篇预告

第二十七篇:JSON数据——配置文件、网站接口、AI返回的结果,到处都是它。长得像字典的JSON到底是什么?json模块怎么用?下一篇见。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注