ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Python图书馆借阅数据分析实战:从指标定义到可视化看板

Python图书馆借阅数据分析实战:从指标定义到可视化看板 简介基于Python的图书馆借阅数据分析项目围绕图书馆历年图书借还、读者与书目等真实数据展开面向图书情报、数据分析初学者以及需要完成毕业设计或课程设计的学生也适合对数据挖掘流程感兴趣的开发者。资源共16个文件约46.45MB其中7个xlsx保存2014—2017年借阅原始数据、图书目录、读者信息和借书排名结果4个py脚本覆盖热门书分析、主题词提取、借书排名变化及帕累托图生成3个txt提供停用词、自定义词典和图书分类简表等辅助语料另有readme说明与png背景示意文件。目前已有2275人学习下载参考价值较高。通过阅读脚本并运行样例可掌握数据清洗、统计汇总、主题词挖掘和可视化呈现的完整思路理解图书馆借阅数据从Excel整理到多维度分析的落地流程脚本按功能拆分、可独立运行便于对照实际数据逐段理解输出能够为同类图书数据分析项目提供直接参考。1. 从一条逾期记录说起图书馆借阅数据分析到底在分析什么拿到“基于python的图书馆借阅数据分析设计与实现”这个题目很多人的第一反应是“用pandas读个Excel画两张柱状图就完事”。但真把一套图书馆流通数据丢给你——几万条借阅流水、上千名读者、几百本高频被借的书——你会发现最难的从来不是画图而是搞清楚“哪张图对图书馆馆长有用哪张图只是自嗨”。这个项目的本质不是写代码而是用python把一张张孤立的借阅流水表变成可支撑采购决策、开放时长调整、阅读推广定位的业务证据链。适合两类人一类是拿它做毕业设计、需要完整交付“数据采集—清洗—分析—可视化”全流程的学生另一类是刚入门数据分析岗位、想用一个非电商场景练手pandas和可视化的人。后面所有内容都围绕一个目标展开让这套分析从“能跑出图”走到“别人愿意照着结论去改采购计划”。2. 先定业务指标再写第一行代码为什么借阅分析的第一步不是数据分析很多初学者把项目做反了——先装环境、先跑通pandas再回头想“我到底要算什么”。等图都画完了导师或馆员问一句“你这个活跃读者是怎么定义的”答不上来。借阅数据分析首先是业务建模其次才是技术实现。你没有真实数据源的时候尤其如此连指标都没定清楚模拟数据都不知道怎么造。2.1 借阅分析的核心指标体系六个口径决定你后面所有代码图书馆借阅数据能挖的角度很多但落到“设计与实现”这个层面真正值得做成功能模块的指标就六个每个都对应一个明确的管理动作。如果指标定义含糊后面每个函数都要返工。指标计算口径建议对应的管理动作借阅量趋势按自然日统计借出册数聚合到周/月判断开放时长和学期节奏是否合理热门图书TOP-N同一ISBN或书名作者去重被借次数降序指导采购复本量和剔旧读者活跃分层按年度借阅次数分0次沉默、1-5次低频、6-20次中频、20次以上高频定位重点阅读推广对象馆藏利用率某分类下被借图书种数 / 馆藏总种数找出“买来没人看”的分类逾期分析实际归还日晚于应还日的记录数量与平均逾期天数评估催还规则和借期设置分类热度按中图法一级分类聚合借阅量优化空间布局和书架摆放这里的核心是“口径先行”。比如“热门图书”到底按ISBN算还是按题名算直接决定结果——同一本书不同版本、同一套书的多册副本如果不做去重统计出来全是《活着》占榜采购看到这种数据是没法下判断的。我一般会明确热门分析按“题名作者”去重后聚合副本差异在馆藏分析里单独看。2.2 设计借阅流水表结构没有真实数据时怎么把字段定规范没有真实业务库的情况下最常见的做法是自己按图书馆管理系统的导出格式模拟一份。先别急着写python打开Excel或SQLite把字段结构定下来。字段设计得规范后面清洗代码能少写一半。一份典型的借阅流水表至少包含这些列借书证号、读者类型、图书ISBN、书名、作者、中图分类号、借出日期、应还日期、实际归还日期、馆藏地。其中最容易出错的是日期字段——很多系统导出是“2024/3/5”这种文本格式python读进来是字符串必须先转datetime再分析这个问题后面避坑章会细说。我一般用SQLite做本地存储而不是直接堆CSV原因很简单几万条数据用CSV也能跑但你要做“按月份筛选按读者分组”这类操作时SQL的声明式写法比pandas链式调用直观得多。建表语句长这样CREATE TABLE borrow_flow ( id INTEGER PRIMARY KEY AUTOINCREMENT, reader_id TEXT NOT NULL, -- 借书证号如 R20230001 reader_type TEXT, -- 学生/教师/校外 isbn TEXT, book_title TEXT, book_author TEXT, category_code TEXT, -- 中图法分类号如 I247.5 borrow_date TEXT, -- 借出日期先存文本清洗时再转 due_date TEXT, -- 应还日期 return_date TEXT, -- 实际归还日期NULL表示未还 location TEXT -- 馆藏地如 三楼文学库 );这个表结构有两点值得注意。第一日期字段在入库阶段故意存成TEXT是为了兼容不同来源的导出格式等数据落库后再统一用python做解析和类型转换避免在源头反复折腾。第二category_code单独成列因为后面分类热度分析要用它做前缀截取——中图法分类号“I247.5”截到一级类目“I”就是文学截到三级“I247”就是当代小说不同粒度对应不同分析视角。2.3 用Python生成模拟借阅数据Faker不够就自己写规则没有真实数据时模拟数据的质量直接决定你的分析代码有没有说服力。用Faker库随机生成一万条“看起来像真的”的借阅记录是常见做法。但纯随机会生成很多业务上不可能的脏数据——比如借出日期是2024年2月30日或者应还日期早于借出日期。所以我的做法是用Faker生成基础信息再用自己的规则去约束时间关系。import random import pandas as pd from datetime import datetime, timedelta random.seed(42) # 固定种子保证可复现 def generate_borrow_records(num10000): 生成模拟借阅流水。 时间规则借出日期在2022-01-01到2024-12-31之间 应还日期 借出日期 30天学生借期 实际归还日期80%概率在应还日期前归还10%逾期1-15天 10%未还置空。 records [] start datetime(2022, 1, 1) end datetime(2024, 12, 31) for i in range(num): borrow_date start timedelta(daysrandom.randint(0, (end - start).days)) due_date borrow_date timedelta(days30) # 归还状态按比例生成模拟真实情况 r random.random() if r 0.8: return_date due_date - timedelta(daysrandom.randint(0, 10)) elif r 0.9: return_date due_date timedelta(daysrandom.randint(1, 15)) else: return_date None records.append({ reader_id: fR{20220000 i}, reader_type: random.choice([本科生, 研究生, 教师]), isbn: f9787{random.randint(1000000, 9999999)}, book_title: random.choice([活着, 百年孤独, 三体, 围城, 小王子]), book_author: random.choice([余华, 马尔克斯, 刘慈欣, 钱锺书, 圣埃克苏佩里]), category_code: random.choice([I247.5, I565.45, TP18, B842.6, K825.6]), borrow_date: borrow_date.strftime(%Y-%m-%d), due_date: due_date.strftime(%Y-%m-%d), return_date: return_date.strftime(%Y-%m-%d) if return_date else None, location: random.choice([三楼文学库, 二楼社科库, 四楼科技库]) }) return pd.DataFrame(records) df generate_borrow_records(10000) df.to_csv(borrow_flow_raw.csv, indexFalse)这段代码的关键在两个地方。第一是random.seed(42)固定随机种子保证每次运行生成的数据一致——这一点在调试时极其重要否则你上午跑出来的结果和下午对不上根本分不清是代码改坏了还是数据变了。第二是归还日期的三种分支分别对应正常归还、逾期归还、未还三种状态目的是让后面“逾期分析”模块有真实感的数据可算而不是所有记录都规规矩矩按时还。实际项目里如果拿到的真实数据存在“应还日期缺失”或“归还日期早于借出日期”这类问题处理逻辑也是在这个环节统一清洗。3. 用Pandas做借阅数据清洗与统计从原始流水到分析宽表数据落库之后真正耗时的环节才开始。很多教程喜欢直接给你看groupby和resample的“标准答案”好像数据天生就是干净的。真实情况是你花在清洗上的时间通常是分析的三倍。这一章把从原始流水到分析宽表的关键操作拆开讲每一步都给出能直接跑通的代码。3.1 读入数据时就把类型定对read_csv的三个关键参数第一步不要急着写业务逻辑先把数据读干净。pandas的read_csv看着简单但如果不指定参数日期会变成字符串空值会变成NaN后面全得返工。import pandas as pd df pd.read_csv( borrow_flow_raw.csv, parse_dates[borrow_date, due_date, return_date], # 自动解析为datetime64 dtype{ reader_id: string, isbn: string, category_code: string }, na_values[, NULL, None] # 把多种空值统一为NaN ) print(df.dtypes) print(df.isna().sum())参数说明parse_dates接受列名列表pandas会自动推断日期格式但推断失败的会变成NaTNot a Time所以读完后一定要用isna()检查每一列的缺失情况。dtype里把编号、ISBN这类列指定为string而不是默认的object是为了避免pandas把它们当分类变量处理。na_values这一步很容易被忽略——真实系统导出时未归还的日期可能是一个空格也可能是字符串“NULL”不统一成NaN后面算逾期天数时就会出现字符串相减直接报错。读完之后做一次最基础的合法性校验逾期天数应该大于等于0实际归还日期不应该早于借出日期。发现脏数据不要急着删先统计有多少条再决定是丢弃还是在后续分析中单独标注。3.2 逾期天数和借阅月份的派生特征让时间口径统一清洗完后就要造派生特征。借阅分析中几乎所有的可视化都建立在两个时间口径上一是“借出日期所在的年月”——用于画趋势图二是“逾期天数”——用于算逾期率。这两个字段不提前算好每画一张图就要重算一次。# 计算逾期天数仅对已归还的图书计算未还的记录单独标记 df[is_returned] df[return_date].notna() df[overdue_days] (df[return_date] - df[due_date]).dt.days df.loc[df[overdue_days] 0, overdue_days] 0 # 提前还的不算逾期 df.loc[~df[is_returned], overdue_days] None # 未还的暂不纳入逾期统计 # 借阅月份统一到YYYY-MM粒度用于按月的聚合分析 df[borrow_month] df[borrow_date].dt.to_period(M).astype(str) # 是否逾期布尔标记后面算逾期率直接用mean() df[is_overdue] df[overdue_days] 0 df.loc[df[is_overdue].isna(), is_overdue] False这里有个口径决策要特别说明提前归还的书逾期天数在计算上是负的统计平均值时会把真正的逾期天数稀释掉所以一律归零。未归还的书严格来说处于“逾期中”状态但因为它还没有结束无法知道最终逾期多少天所以先不纳入逾期天数统计只在“当前在借且已超期”这个单独指标里体现。这个区分听起来细但图书馆馆员真的会问——你说逾期率10%包不包括借了半年不还的那批回答不上来就尴尬了。3.3 三层统计代码按月趋势、热门图书TOP-N、读者活跃分层核心统计逻辑就三个模块每个模块对应第2章定义的一个指标。这三段代码是整套系统的主干建议写成独立函数不要都挤在同一个脚本里。我一般会做成一个analysis.py每个函数接收DataFrame返回统计结果方便后面接可视化。# 模块一月度借阅趋势 def monthly_trend(df): 按自然月统计借阅量返回带月份字符串的DataFrame trend df.groupby(borrow_month).size().reset_index(nameborrow_count) trend[borrow_month] pd.to_datetime(trend[borrow_month]) trend trend.sort_values(borrow_month) return trend # 模块二热门图书TOP-N按题名作者去重多副本合并计算 def hot_books(df, top_n10): hot ( df.groupby([book_title, book_author]) .size() .reset_index(nameborrow_count) .sort_values(borrow_count, ascendingFalse) .head(top_n) ) return hot # 模块三读者活跃分层 def reader_segmentation(df, yearNone): 按读者ID统计借阅次数并按阈值分箱。 year参数用于筛选指定年份None表示全量。 if year: df df[df[borrow_date].dt.year year] reader_stat df.groupby(reader_id).size().reset_index(nameborrow_count) bins [0, 1, 5, 20, float(inf)] labels [沉默读者, 低频读者, 中频读者, 高频读者] reader_stat[level] pd.cut(reader_stat[borrow_count], binsbins, labelslabels, rightFalse) return reader_stat trend monthly_trend(df) hot hot_books(df, 10) reader reader_segmentation(df, year2023)三个函数各有一个值得注意的参数。monthly_trend里把“YYYY-MM”字符串再转回datetime并排序是为了保证画折线图时横轴顺序正确否则“2024-02”会排在“2024-10”前面。hot_books的groupby用的是题名加作者组合而不是ISBN——正如第2章说的多副本和不同版本要合并否则一本《活着》的二十个副本会把榜单占满。reader_segmentation里的pd.cut用的区间是左闭右开rightFalse所以借1次算低频、借20次算高频边界值不会落到两个箱子。bins和labels这个字典实际项目里我会放到配置文件里方便馆员自己调阈值不用改代码。3.4 分类利用率和分类热度截取中图法分类号的一级类目最后一个统计模块是分类分析。中图法分类号的规律是第一位字母代表大类I文学、T工业技术、B哲学等后续数字代表细分类目。分析时至少要看两个粒度一级类目看宏观结构三级类目看具体热点。这段代码顺带演示了pandas的字符串切片和apply配合lambda的用法。# 提取中图法一级分类字母并映射为中文类名 first_level_map { A: 马列主义, B: 哲学, C: 社会科学总论, D: 政治法律, F: 经济, G: 文化科学教育, H: 语言文学, I: 文学, J: 艺术, K: 历史地理, N: 自然科学总论, O: 数理科学, P: 天文学地球科学, Q: 生物科学, R: 医药卫生, T: 工业技术, TP: 自动化计算机技术, X: 环境科学, Z: 综合性图书 } def get_first_level(code): 截取分类号的首字母部分二级类如TP需要特判 if pd.isna(code): return 未知 code str(code).strip() if code.startswith(TP): return TP return code[0] if code else 未知 df[first_level] df[category_code].apply(get_first_level) df[first_level_name] df[first_level].map(first_level_map) # 一级类目借阅量统计 category_stat ( df.groupby([first_level, first_level_name]) .size() .reset_index(nameborrow_count) .sort_values(borrow_count, ascendingFalse) ) # 馆藏利用率每个一级类目下被借过的书种数 占 全部书种数 的比例 total_books_by_cat df.groupby(first_level)[isbn].nunique() borrowed_books_by_cat df[df[borrow_count] 0].groupby(first_level)[isbn].nunique()常见做法是直接取code[0]但中图法里还有个特殊情况——“TP”是“自动化技术、计算机技术”的二级类它的首字母“T”代表工业技术如果直接按第一位分计算机类图书会被算进工业技术大类里去和实际认知出入很大。这个特判看着小但图书馆馆员一眼就能看出来你懂不懂分类法答辩时属于加分细节。另外nunique这个聚合函数专门用来统计不重复值数量算馆藏利用率时它是主力比size()所有记录数更符合“多少种书被借过”的语义。4. 可视化方案选型与核心图表实现从数据到能交差的看板统计结果还是冰冷的数字表格做成图才能让人看明白。这一章解决两个问题图表用哪个库画、怎么画才有效。选型上的决策直接影响后面的开发效率和呈现效果这个环节翻车的人不在少数。4.1 Matplotlib还是Pyecharts选型理由和适用边界可视化库基本绕不开三个选择Matplotlib、Pyecharts、Plotly。我的建议很直接单纯画静态图用Matplotlib要做交互动效和HTML看板用PyechartsPlotly按项目要求选。三者的核心差异在渲染路径——Matplotlib渲染成静态图片嵌入Word和论文里最省事Pyecharts渲染成HTML页面图表自带悬浮提示和数据缩放滑块演示时效果更好Plotly的优势在交互大数据量但对图书馆这种几万条数据的场景属于杀鸡用牛刀。做一个可交差的“设计与实现”项目我一般推荐Pyecharts做一张综合看板HTML左边一列月度趋势折线图右边热门图书条形图下边读者分层饼图和分类热度柱状图。这样整个项目有一个最终交付物而不是散落一地的PNG图片。Matplotlib则作为辅助用来画那些需要细致控制细节、准备打印到论文里的图。如果你看到这里还在纠结一个更简单的判断标准毕设/答辩场景优先Pyecharts因为交互效果在一屋子静态PPT里很显眼数据挖掘课设场景优先Matplotlib因为和机器学习特征分析图的风格统一。4.2 月度借阅趋势的三种粒度日、周、月分别适合什么场景趋势图最容易犯的错是粒度选错。直接按日画一万条数据折线图会变成一堵毛刺墙什么都看不出来。按年画又太粗学期内的波峰波谷全丢了。课堂和项目里最常用的三种粒度按日用于观察突发峰值比如读书日当天按周围观周期性比如每周一和周五的差异按月看学期节奏。我一般推荐默认按周聚合统计口径是“借出时间所在的自然周”pandas的resample可以直接干这件事。import matplotlib.pyplot as plt from matplotlib import rcParams # 设置中文字体防止乱码Windows用SimHeimacOS用Arial Unicode MS rcParams[font.sans-serif] [SimHei, Arial Unicode MS] rcParams[axes.unicode_minus] False # 先把借书日期设为索引才能用resample df[borrow_date] pd.to_datetime(df[borrow_date]) trend_daily df.set_index(borrow_date)[reader_id].resample(D).count() trend_weekly df.set_index(borrow_date)[reader_id].resample(W-MON).count() trend_monthly df.set_index(borrow_date)[reader_id].resample(MS).count() # 周粒度加一个4周滚动平均平滑掉偶然波动 trend_weekly_smooth trend_weekly.rolling(4, centerTrue).mean() fig, ax plt.subplots(figsize(12, 5)) ax.plot(trend_weekly.index, trend_weekly.values, alpha0.5, label每周借阅量) ax.plot(trend_weekly_smooth.index, trend_weekly_smooth.values, label4周滚动平均) ax.set_title(图书馆每周借阅量趋势含滚动平均) ax.legend() plt.tight_layout() plt.savefig(trend_weekly.png, dpi200)resample的三个参数值得记牢“D”是自然日“W-MON”是每周一作为一周的结束来做聚合“MS”是每月第一天。选择W-MON而不是默认的W-SUN是为了符合国内“周一到周日”的自然周认知。rolling(4, centerTrue)计算4周中心滚动平均也就是用前后各两周的数据平滑当前点能明显消除寒暑假那种“突然掉到0再突然涨回来”的锯齿感让趋势更可读。借阅趋势图不加滚动平均是最常见的翻车点——你拿给馆员看对方只会说“这图怎么跟心电图一样”。4.3 热门图书TOP-N和读者分层的Pyecharts实现Pyecharts的最大优势是配置项体系化每个图表类型对应一个类先add数据再set_global_opts设置全局选项。下面这段代码直接输出一个HTML文件双击就能在浏览器打开不需要额外起服务。from pyecharts.charts import Bar, Pie, Calendar from pyecharts import options as opts # 热门图书TOP-10条形图 hot hot_books(df, 10) bar ( Bar() .add_xaxis(hot[book_title].tolist()) .add_yaxis(借阅次数, hot[borrow_count].tolist()) .set_global_opts( title_optsopts.TitleOpts(title热门图书TOP-10), xaxis_optsopts.AxisOpts(axislabel_optsopts.LabelOpts(rotate30)), yaxis_optsopts.AxisOpts(name借阅次数), datazoom_opts[opts.DataZoomOpts()], # 数据太多时启用缩放滑块 ) .set_series_opts(label_optsopts.LabelOpts(positionright)) .reversal() # 横向条形图书名长时更易读 ) # 读者活跃分层饼图 reader reader_segmentation(df, year2023) level_counts reader[level].value_counts() pie ( Pie() .add(, [list(z) for z in zip(level_counts.index, level_counts.values)]) .set_global_opts(title_optsopts.TitleOpts(title2023年读者活跃分层)) .set_series_opts(label_optsopts.LabelOpts(formatter{b}: {c}人 ({d}%))) ) bar.render(hot_books.html) pie.render(reader_level.html)Pyecharts里最常用的三个配置项都在这段代码里。rotate30控制x轴标签旋转书名太长叠在一起时必须用datazoom_opts在数据量大时加一个拖拽缩放条reversal()把条形图转成横向让书名在y轴完整显示。Formatter里“{b}”是名称占位符、“{c}”是数值、“{d}”是百分比这是Pyecharts的通用模板语法。注意.这方法链是一路点下来的中间任何一个add或set都不能漏——漏了set_series_opts图表也能渲染但标签位置和格式就全是默认值了。5. 避坑指南借阅数据分析最容易翻车的五个细节这一章写的都是真实项目里反复踩过的坑。每个坑乍一看都是小事但任何一个都能让你在交付前夜彻夜难眠。按“现象—原因—解决”的结构写方便你直接定位。5.1 控制台中文乱码Windows下read_csv读UTF-8文件报错现象用pandas读CSV文件是用Excel另存的print(df.head())直接抛UnicodeDecodeError或者中文变成一堆乱码。原因Excel在Windows上默认用GBK编码保存CSV而pandas的read_csv默认用UTF-8解码。两套编码对不上必然报错。解决要么在read_csv里显式指定encoding要么在Excel导出时选择UTF-8。我一般这样处理# 方法一先探测编码再读取 import chardet with open(borrow_flow_raw.csv, rb) as f: raw f.read(10000) encoding chardet.detect(raw)[encoding] df pd.read_csv(borrow_flow_raw.csv, encodingencoding)# 方法二统一转换为UTF-8后再交给pandas with open(borrow_flow_raw.csv, r, encodinggbk, errorsignore) as f: content f.read() with open(borrow_flow_converted.csv, w, encodingutf-8) as f: f.write(content)方法一适合“不知道源文件什么编码”的情况chardet会探测出一个最可能的编码名但不能保证100%准确方法二适合你明确知道是从图书馆管理系统导出的Windows文件直接按GBK读再转UTF-8落盘。这种方法有个细节errorsignore会把个别无法解码的字符直接扔掉如果文件里恰好有生僻字转换后可能缺字。稳妥的做法是errorsreplace把坏字符替换成问号至少能发现哪里丢了数据。5.2 日期列读进来是字符串所有时间计算全报错现象read_csv之后borrow_date列的类型是object而不是datetime64一跑df[due_date] - df[borrow_date]就报TypeError。原因源文件里的日期格式是“2024/3/5”或者“2024年3月5日”pandas的parse_dates参数只对标准格式2024-03-05有效遇到斜杠和中文字符就识别失败。解决读入后手动指定格式解析。这里有个血泪经验不要用infer_datetime_format它在复杂格式下性能差且不可靠。直接写清楚格式串df[borrow_date] pd.to_datetime(df[borrow_date], format%Y/%m/%d, errorscoerce) df[due_date] pd.to_datetime(df[due_date], format%Y/%m/%d, errorscoerce) df[return_date] pd.to_datetime(df[return_date], format%Y/%m/%d, errorscoerce) # 解析失败的行会变成NaT检查一下有多少 bad_date_count df[borrow_date].isna().sum() print(f借出日期解析失败 {bad_date_count} 行)format参数的作用是告诉pandas日期的精确结构,比让pandas自己猜要快得多。errorscoerce把格式不匹配的值变成NaT而不是抛异常这样至少整个分析流程能继续跑下去最后再统一处理这些坏行。解析完一定要数一下失败的行数——如果你不检查后续所有按时段的聚合结果都会悄悄变少而且你根本发现不了。5.3 逾期天数出现负数和极端值现象算逾期天数的代码逻辑没问题但统计结果出现-5天、-30天甚至还有三位数的正数比如逾期365天。原因负数来自提前归还——应还日期是4月10日读者4月5日就还了天数差就是-5。极端正数有两个来源一是闭馆日顺延没在数据里体现二是一些图书实际是跨学期借阅读者根本没在系统里做续借操作系统就认为逾期了一整年。解决负数统一归零处理。极端正数不建议直接删而是单独标记为“超长逾期”在统计逾期率时给出两个口径——剔除超长逾期的常规逾期率和包含全部记录的毛逾期率让决策者自己判断用哪个。直接删数据是最偷懒的做法答辩时被问到“你删了多少条、为什么删”就答不上来。# 两个口径的逾期统计 df[overdue_days] df[overdue_days].clip(lower0) # 负值归零 normal_overdue df[(df[is_returned]) (df[overdue_days] 60)] coarse_overdue df[(df[is_returned])] print(f常规逾期率≤60天: {normal_overdue[is_overdue].mean():.2%}) print(f毛逾期率含超长: {coarse_overdue[is_overdue].mean():.2%})5.4 resample后的时间序列出现NaN空洞现象按周聚合的趋势图中间某些周没有数据折线图直接断掉或者出现奇怪的跳变。原因寒暑假期间图书馆闭馆没有借阅记录resample按连续时间轴重采样后无数据的周期自动填充为NaN。如果不处理matplotlib的plot默认会跳过NaN图就断了。解决根据分析意图决定填充方式。趋势分析用前值填充还是零填充取决于你想表达什么——想表达“这段时间没开馆”就填零想表达“假设延续之前的水平”就前值填充。我一般填零更真实。trend_weekly trend_weekly.fillna(0)这个坑的隐蔽之处在于如果只用真实记录直接画图不经过resample根本不会出现NaN看起来一切正常一旦换成resample问题才暴露。所以导航顺序很重要——先resample再填充后画图不要跳步。5.5 Pyecharts生成的HTML白屏或图表不显示现象代码跑通了render()也执行了但双击HTML打开是空白控制台报JS错误。原因Pyecharts渲染出的HTML会从CDN加载ECharts的JS库。电脑没联网或者CDN地址被屏蔽时图表就渲染不出来。这是最容易让人误判为“代码写错了”的问题其实是环境问题。解决安装pyecharts时顺带把本地JS库装好禁用远程加载。from pyecharts.globals import CurrentConfig, NotebookType # 使用本地资源不依赖CDN CurrentConfig.ONLINE_HOST ./assets/ # 需要先把echarts.min.js放到该目录更省事的方式是直接装pyecharts-snapshot用命令行把HTML渲染成图片pyecharts render hot_books.html hot_books.png如果只是想在答辩PPT里用图片这个方案最稳。不要等到答辩现场才发现白屏——提前把HTML发给同事在其他电脑上打开试一次。6. 最后一公里把分析交给图书馆员使用的交付方案很多项目死在“最后一公里”分析做完了、图表画好了但图书馆员拿到手不会用。你不可能每次都用Jupyter Notebook现场敲代码。这套借阅分析系统要真正落地至少要走完“脚本化—参数化—输出产物”这一步。这是很多教程不会写但项目必须解决的部分。6.1 用命令行参数控制分析维度不用改代码就能换个年份最常见的做法是把分析脚本改成接受命令行参数年份、TOP-N的N、输出目录全部参数化。这样馆员只需要记一条命令而不是打开代码改三个变量。import argparse def main(): parser argparse.ArgumentParser(description图书馆借阅数据分析) parser.add_argument(--year, typeint, default2024, help统计年份默认2024) parser.add_argument(--topn, typeint, default10, help热门图书取前几条默认10) parser.add_argument(--output, typestr, defaultreport, help输出目录名) args parser.parse_args() df pd.read_csv(borrow_flow_clean.csv, parse_dates[borrow_date, due_date, return_date]) trend monthly_trend(df[(df[borrow_date].dt.year args.year)]) hot hot_books(df, args.topn) print(f{args.year}年共借出{trend[borrow_count].sum()}册) print(f热门图书TOP{args.topn}: {hot[book_title].tolist()}) # 生成报告 os.makedirs(args.output, exist_okTrue) render_report(args.year, args.topn, args.output) if __name__ __main__: main()argparse是python标准库不涉及额外安装。这里把三条信息打出来总借阅量、热门榜单、输出位置让使用者有即时反馈。实际运行命令是python analyze.py --year 2023 --topn 10 --output report_2023。这样“换一年重新分析”的操作从“改代码”变成“改参数”至少能教会非技术背景的馆员自己跑。6.2 看板合并与半自动更新让数据自己流向决策者分析系统的终点不是一个孤立的HTML文件而是一份能自动更新的汇总看板。我的习惯做法是将多张图表合并到同一个HTML模板里用python-docx或直接写HTML片段生成带标题、结论建议、图表的完整报告。再加一步自动化写一个批量脚本循环遍历需要统计的年份范围批量产出所有年度报告配合操作系统的定时任务就可以每周自动更新。# 合并多张图表到一张综合看板 from pyecharts.components import Table from pyecharts import options as opts from pyecharts.charts import Tab tab Tab() tab.add(bar, 热门图书) tab.add(pie, 读者分层) tab.add(calendar_heatmap, 借阅日历) tab.render(综合看板.html) print(看板已生成综合看板.html)Tab组件把多个图放在同一个HTML的不同标签页里顶部可以切换比一张图一张图分散着给馆员要直观得多。这里我的血泪经验是与其追求复杂的自动化推送发邮件、发消息不如先把看板物理落地。哪怕只有一个HTML文件放在共享盘里每周固定覆盖更新都比一百张散落的PNG强。我在本地项目里踩过的坑是给图表文件加了时间戳后缀结果两周后盘里躺了二十个同名不同时间的文件馆员根本不知道该看哪个。后来改成固定文件名覆盖更新反而谁都找得到了。这套方案做下来你会发现python的分析价值不在算法多高级而在于把“每个月手工导Excel再透视”变成了一条命令的事。图书馆的借阅数据每周都在涨你的分析系统如果能跟着数据自动更新才叫真正的“设计与实现”。希望这一整套从指标定义到交付落地的思路能帮你在自己的项目里少走几步弯路。本文还有配套的精品资源点击获取
返回列表