基于Python的图书馆借阅数据分析:从流水到决策看板
简介这份资源面向具备一定Python基础、希望上手真实数据分析项目的高校学生与数据挖掘初学者围绕图书馆借阅业务场景提供从数据清洗、主题词提取到可视化呈现的完整实现思路可用于课程设计、毕业设计或数据分析练手。压缩包共16个文件约46.45MB其中7个xlsx为2014至2017年图书借还、图书目录与读者信息等原始数据4个py脚本分别完成主题词提取、帕累托图生成、热门书分析与排名变化计算另有3个txt提供停用词、图书馆新词及《中国图书馆图书分类法》简表并附readme说明与背景掩码图片便于快速理解目录结构与运行流程。目前已有2275人学习下载读者可据此掌握借阅数据的多维统计方法、热门图书TOP20排名演变分析以及帕累托法则在馆藏优化中的应用同时获得可复用的数据预处理与可视化脚本适合作为数据分析入门到进阶的实战参考。1. 基于 Python 的图书馆借阅数据分析从借阅流水到决策看板一条能跑通的路流通部每个月末把 Excel 导出来几万行借阅记录躺在那里字段有借书时间、还书时间、读者类型、馆藏地、中图分类号但没人真正把它当数据看。馆长想知道的是哪些书在空转、哪些读者在流失、采购经费该往哪个类目倾斜。这些问题用 Excel 透视表能答一半剩下那一半——比如「借阅量下降是读者少了还是人均借得少了」——就得靠 Python 把数据拆开算。这篇讲的就是基于 Python 的图书馆借阅数据分析设计与实现从原始流水到可复现的指标口径再到能交给业务方看的图表。适合会一点 Python 基础、手上有借阅导出数据、想把它做成一套稳定分析流程的图书馆技术人员或数据方向的学生。整套东西不依赖重型平台一台能装 Python 的机器就够。2. 先把数据摸清楚借阅流水的字段、口径与清洗2.1 一份典型的借阅导出表长什么样不同图书馆系统的导出格式差别很大但核心字段基本一致。我见过的最常见结构是这样一条记录代表一次借阅行为包含读者证号、条码号图书唯一标识、书名、中图分类号、借出日期、应还日期、归还日期、续借次数、馆藏地点、读者类型。有的系统还会给一个「操作类型」字段把借书、还书、续借混在同一张表里这时候第一步不是分析是拆表。先明确三个口径后面所有指标都建立在这上面口径定义容易踩的坑借阅次数一条借出记录算一次续借是否算新借阅各馆规定不同在借时长归还日期减借出日期未归还记录的归还日期为空活跃读者统计周期内至少有一次借阅的读者证号重复、临时证要不要算这三个口径不统一后面算出来的「人均借阅量」「平均在借天数」全是错的。我一般会在分析开始前把口径写成注释放在脚本头部谁改谁负责。2.2 用 pandas 做第一轮清洗拿到数据先别急着画图先看脏在哪。下面这段是通用的第一轮体检代码import pandas as pd import numpy as np # 读入注意编码国图系统导出常见 gbk df pd.read_csv(borrow_records.csv, encodinggbk, dtypestr) # 统一列名去掉空格 df.columns [c.strip() for c in df.columns] # 看整体规模和缺失 print(df.shape) print(df.isnull().sum().sort_values(ascendingFalse).head(10)) # 日期字段转 datetimeerrorscoerce 把非法值变 NaT for col in [借出日期, 应还日期, 归还日期]: df[col] pd.to_datetime(df[col], errorscoerce) # 借出日期为空的行直接丢这是硬伤 df df.dropna(subset[借出日期]) # 去重同一读者同一本书同一天借出多半是重复导出 df df.drop_duplicates(subset[读者证号, 条码号, 借出日期])逻辑说明dtypestr先全部按字符串读进来避免 pandas 自动把证号里的前导零吃掉errorscoerce是关键遇到「0000-00-00」这种脏日期不会直接报错中断而是变成 NaT 让你后面统一处理。参数上drop_duplicates的 subset 要按你系统的实际主键来定有的馆用「条码号借出日期」就够有的必须带上读者证号。清洗完做一次分布检查看借出日期有没有集中在某几天可能是系统迁移导致的假数据看中图分类号有没有空值。这一步花十分钟能省后面两小时的返工。2.3 分类号归一化别让 I247.5 和 I247.57 分成两类中图分类号是分析馆藏结构的核心维度但原始数据里它经常是完整索书号比如「I247.5/1234」。分析时通常只取到二级或三级类目。常见做法是截取首字母加前两位数字def normalize_clc(code): if pd.isna(code): return 未知 code str(code).strip().upper() # 取第一个字母 letter code[0] if code[0].isalpha() else 其他 # 取字母后的数字部分前两位 digits .join([c for c in code[1:] if c.isdigit()])[:2] return letter digits if digits else letter df[类目] df[中图分类号].apply(normalize_clc)这样 I247.5 和 I247.57 都会归到 I24文学类下的细分统计才有意义。参数上取几位数字取决于你的分析粒度做采购建议一般到二级类目一个字母加两位数字就够做典藏调拨可能要细到四级。3. 指标怎么算借阅量、读者分层与馆藏周转3.1 借阅量趋势同比和环比要分开看最基础的指标是按月借阅次数。但只看绝对值会被学期节奏带偏寒暑假天然低。所以至少要算两个衍生指标环比跟上个月比和同比跟去年同月比。同比能过滤掉学期因素环比能快速发现异常。df[借出年月] df[借出日期].dt.to_period(M) monthly df.groupby(借出年月).size().reset_index(name借阅次数) monthly[环比] monthly[借阅次数].pct_change().round(4) monthly[同比] monthly[借阅次数].pct_change(periods12).round(4)pct_change(periods12)就是同比前提是你的数据至少覆盖两年。如果只有一年数据同比列全是 NaN这时候别硬算老老实实标注「数据不足」。我见过有人拿半年数据算同比结论完全站不住。3.2 读者分层用 RFM 思路做借阅活跃度RFM 本来是电商模型搬到图书馆一样好用。R 是最近一次借阅距今多久F 是统计周期内借阅次数M 在这里可以换成借阅品类数借得越杂说明探索性越强。三个维度各分三档组合出读者类型。snapshot df[借出日期].max() pd.Timedelta(days1) rfm df.groupby(读者证号).agg( R(借出日期, lambda x: (snapshot - x.max()).days), F(借出日期, count), M(类目, nunique) ).reset_index() # 按分位数分档避免人为定阈值 rfm[R分] pd.qcut(rfm[R], 3, labels[3, 2, 1]).astype(int) rfm[F分] pd.qcut(rfm[F].rank(methodfirst), 3, labels[1, 2, 3]).astype(int) rfm[M分] pd.qcut(rfm[M].rank(methodfirst), 3, labels[1, 2, 3]).astype(int)注意qcut遇到大量重复值会报错所以 F 和 M 先rank(methodfirst)打散。R 分的方向要反过来最近借阅的R 小给高分。分完之后可以给读者打标签R 高 F 高的是核心读者R 低 F 低的是流失预警R 高 F 低的是新读者或低频读者。这套分层直接决定了后续的推送策略和采购参考。3.3 馆藏周转率被借走的书和没人碰的书周转率是采购决策最直接的依据。算法是某类目在统计周期内的借阅次数除以该类目的馆藏册数。但馆藏册数往往不在借阅流水里需要单独从馆藏系统导一张表按类目聚合后跟借阅数据 merge。# holdings 是馆藏表含类目和册数 holdings pd.read_csv(holdings.csv, encodinggbk) holdings[类目] holdings[中图分类号].apply(normalize_clc) holding_count holdings.groupby(类目)[条码号].nunique().reset_index(name馆藏册数) borrow_count df.groupby(类目).size().reset_index(name借阅次数) turnover borrow_count.merge(holding_count, on类目, howleft) turnover[周转率] (turnover[借阅次数] / turnover[馆藏册数]).round(2) turnover turnover.sort_values(周转率, ascendingFalse)这里有个坑借阅流水里的类目和馆藏表里的类目必须用同一个归一化函数否则 merge 出来一堆 NaN。另外周转率高于 1 说明平均每册被借超过一次低于 0.1 基本就是沉睡馆藏。我一般会把周转率低于 0.1 且馆藏册数大于 50 的类目单独拉出来作为采购压缩的候选。4. 可视化与交付让业务方看得懂的分析结果4.1 用 matplotlib 出一套静态图别一上来就上大屏很多教程一上来就教 Plotly Dash 或者 ECharts 大屏但实际工作中业务方最常要的是一份能贴进 Word 的图。matplotlib 配好中文字体出的图清晰、可复现、不依赖网络。import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False fig, ax plt.subplots(figsize(10, 5)) ax.plot(monthly[借出年月].astype(str), monthly[借阅次数], markero) ax.set_title(月度借阅量趋势) ax.set_xlabel(月份) ax.set_ylabel(借阅次数) plt.xticks(rotation45) plt.tight_layout() plt.savefig(monthly_trend.png, dpi150)SimHei是 Windows 自带黑体Linux 上要换成WenQuanYi Micro Hei或手动指定字体文件路径。dpi150是打印和屏幕都够用的平衡点再高文件就大了。tight_layout防止中文标签被裁掉这个不加经常翻车。4.2 类目热力图一眼看出哪些类目在冷热交替单看周转率排名不够直观把类目和月份交叉做一张借阅热力图能看出某些类目是不是只在开学季热。用 seaborn 的 heatmap 最省事import seaborn as sns pivot df.pivot_table(index类目, columns借出年月, values条码号, aggfunccount).fillna(0) # 只保留借阅量前 15 的类目否则图太挤 top_cats pivot.sum(axis1).nlargest(15).index pivot pivot.loc[top_cats] plt.figure(figsize(14, 6)) sns.heatmap(pivot, cmapYlOrRd, linewidths0.5) plt.title(各类目月度借阅热力图) plt.tight_layout() plt.savefig(category_heatmap.png, dpi150)参数上cmap选 sequential 色系YlOrRd、Blues比 diverging 色系更合适因为借阅量没有负数。linewidths加一点白线让格子分明。如果类目太多一定要截断否则图没法看。4.3 把结果落成 Excel 报表附口径说明业务方最终要的是能筛选、能排序的文件。用 pandas 的 ExcelWriter 把多个 sheet 写进一个文件第一个 sheet 放口径说明后面放明细。with pd.ExcelWriter(借阅分析报告.xlsx, engineopenpyxl) as writer: pd.DataFrame({ 指标: [借阅次数, 在借时长, 活跃读者], 口径: [一条借出记录算一次, 归还日期-借出日期, 周期内至少借一次] }).to_excel(writer, sheet_name口径说明, indexFalse) monthly.to_excel(writer, sheet_name月度趋势, indexFalse) turnover.to_excel(writer, sheet_name类目周转, indexFalse) rfm.to_excel(writer, sheet_name读者分层, indexFalse)engineopenpyxl是写 xlsx 的默认选择如果数据超过百万行要换xlsxwriter并开constant_memory。口径说明这个 sheet 千万别省我吃过亏——报告交上去三个月后没人记得「活跃读者」当时是怎么定义的扯皮扯了半天。5. 避坑与排查那些让我返工三次的问题5.1 日期解析静默失败趋势图少了一大截现象月度趋势图某几个月数据突然掉到接近零但原始表里明明有记录。原因pd.to_datetime遇到「2023/3/5」和「2023-03-05」混用不会报错但某些带中文的日期如「2023年3月5日」会变成 NaT随后被dropna悄悄删掉。解决转换后先统计 NaT 数量print(df[借出日期].isna().sum())如果非零就回去看原始格式必要时用format参数显式指定或者写自定义解析函数。5.2 读者证号前导零丢失活跃读者数虚高现象读者分层结果里同一个人的证号出现两次一次带零一次不带。原因读 CSV 时没指定dtypestrpandas 把「0012345」当成整数 12345。解决所有 ID 类字段一律dtypestr读入或者在read_csv里用converters{读者证号: str}单独指定。这个坑在读者证号是纯数字的馆特别常见。5.3 续借记录重复计数借阅量虚增现象某本书借阅次数异常高查原始记录发现同一条码号连续多条记录日期只差几天。原因系统把每次续借也导成一条借出记录。解决先确认业务口径——如果续借不算新借阅就要按「读者证号条码号」分组只保留最早那条借出记录或者用「操作类型」字段过滤。这个必须跟流通部确认不能自己拍脑袋。5.4 中文字体缺失图上全是方框现象本地跑得好好的脚本换台机器或部署到服务器上图里中文全变方框。原因SimHei是 Windows 字体Linux 服务器上没有。解决把字体文件如SimHei.ttf随项目一起放用font_manager.fontManager.addfont()显式加载再设rcParams。或者干脆用WenQuanYi Micro Hei这类 Linux 常见中文字体。别指望服务器上一定有中文字体。5.5 周转率算出无穷大除零没处理现象某些类目周转率显示inf排序后跑到最前面。原因该类目在借阅表里有记录但在馆藏表里册数为零可能是数据没同步。解决merge 之后加一句turnover turnover[turnover[馆藏册数] 0]或者用np.where把除零结果置为 NaN。同时要排查为什么馆藏表缺这些类目可能是馆藏导出时过滤了某些状态的书。6. 把分析做成能定期跑的脚本参数化与调度前面所有代码如果每次都要手动改路径、改日期范围用不了三次就会烦。我的习惯是把可变部分全部抽成配置脚本只读配置。下面是一个最小可用的配置结构# config.py CONFIG { borrow_file: data/borrow_2024.csv, holding_file: data/holdings.csv, encoding: gbk, date_range: (2024-01-01, 2024-12-31), top_n_categories: 15, output_dir: output/, turnover_threshold: 0.1, }主脚本读这个字典所有路径和阈值都从这里取。这样换一批数据只需要改配置不用动分析逻辑。日期范围在清洗阶段就过滤掉避免后面每个指标都重复过滤。再进一步把整个流程包成一个函数用if __name__ __main__:调用方便被调度工具拉起。Linux 上用 cronWindows 上用任务计划程序每周或每月跑一次输出带时间戳的报告文件。我一般会在输出文件名里带上运行日期比如借阅分析报告_20241201.xlsx这样历史报告不会互相覆盖出问题能回溯。验证分析结果对不对我有个笨办法但很管用随机抽三条原始记录手动算一遍它们的在借时长和所属类目跟脚本输出对一遍。三条对上了基本说明清洗和归一化逻辑没问题。这个习惯帮我抓到过好几次分类号截取位数不对的问题。最后说个我自己的教训别在第一次交付时就追求指标大而全。我最早那版报告塞了二十多个指标业务方翻了两页就放下了。后来砍到五个——月度趋势、类目周转、读者分层、沉睡馆藏清单、活跃读者数——反而每次都被追着要。分析的价值不在算得多在于算的那个数有人用。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →