简介:《基于Python的图书馆借阅数据分析设计与实现》是一份专科和本科毕业论文资源,面向计算机、信息管理及相关专业毕业生,旨在帮助完成图书馆数据类课题。课题以图书馆借阅数据为对象,完整覆盖数据爬取、清洗、存储、统计建模与可视化展示等环节,同时结合Django框架搭建可交互的借阅分析系统,帮助理解从原始数据到业务分析结果的全过程。论文结构完整,涵盖摘要、关键词、绪论、国内外研究现状、相关技术与工具、数据获取与处理、数据分析方法等章节,详细介绍了Pandas、Numpy、Matplotlib、Scikit-learn等主流数据分析库在数据清洗、特征工程、模型建立中的应用,并对数据可视化与统计挖掘方法给出了可操作示例,可直接迁移至同类课题。资源包仅包含1个DOCX文件,大小约35KB,全文已做降重处理,字数超过一万,内容组织清晰,既可作为毕业设计的框架参考,也能支撑论文写作、系统设计与功能模块的改编复用。目前该资源已有473人浏览学习,适合需要快速搭建论文体系、借鉴数据采集与分析方法,或想结合Python与网络爬虫、Django进行综合开发的读者使用。
1. 基于python的图书馆借阅数据分析到底在解决什么问题
图书馆借阅系统每天产生少则几百、多则上万条借还流水,常见的管理做法是让管理员在excel里做透视表,统计每月借了多少册、哪些书最热门。这种模式只能回答“发生了什么”,回答不了“为什么会发生”和“接下来会怎样”。基于python的图书馆借阅数据分析,正是把散落在借阅流水、读者表、图书表里的数据汇成一张可分析的宽表,然后用可复现的脚本完成时间序列分析、读者分层建模和可视化报表输出。它解决的是三个具体问题:第一,借阅数据格式混乱怎么清洗;第二,借阅行为用什么指标量化;第三,分析结果如何变成管理员能直接看的图形和表格。这套方法既能在毕业设计项目中作为完整实现样例,也能用于图书馆或资料室的日常数据运营。本文所有代码都围绕最标准的借阅流水格式展开,在jupyter notebook中逐段执行,或者在pycharm里按脚本运行,不需要额外安装数据库组件,只依赖pandas和几个常用库。
2. 借阅数据清洗与合并:从流水明细到分析宽表
借阅数据很少是“开箱即用”的。真实的借阅系统导出的excel通常包含多张表,且字段命名不统一、时间格式混乱、证号有前导零丢失问题。正式开始分析前,必须先完成字段确认、脏数据清理和表间合并。这一步做不好,后面的所有指标都会失真。
2.1 借阅流水表的字段结构与常见脏数据
最常见的借阅相关表有三张:借阅流水表、读者信息表、图书信息表。我一般会先按下列字段清单核对原始数据:
| 表名 | 常见字段 | 说明 |
|---|---|---|
| 借阅流水表 | 借阅证号、图书条码、借书时间、还书时间、续借次数 | 每条记录代表一次借阅行为 |
| 读者信息表 | 借阅证号、读者类型、学院/部门、年级/职称 | 读者属性,用于分组分析 |
| 图书信息表 | 图书条码、书名、中图分类号、馆藏地点、出版年 | 图书属性,用于分类挖掘 |
三张表通过借阅证号和图书条码关联。最容易踩的坑是:借阅证号在原始数据里被excel识别成数字,前导零被吃掉,导致merge时匹配失败;借书时间和还书时间可能是字符串,格式从2023/01/05到2023-01-05 10:23:45都有。还书时间为空通常代表书还没还,这个空值在处理时不能直接删除,而是要单独标记成“在借”。此外,系统在办理续借时可能生成两条一模一样的记录,去重必须放在所有统计之前。
2.2 用pandas统一时间格式并进行去重
拿到三张表的原始excel后,第一步是统一读取方式。下面这段代码用于读入借阅流水,并完成基础清理:
import pandas as pd # 读取借阅流水,借书时间和还书时间直接按日期时间列解析 df_borrow = pd.read_excel( "borrow.xlsx", parse_dates=["借书时间", "还书时间"], # 自动识别多种时间格式 dtype={"借阅证号": str} # 证号必须按字符串读入,防止前导零丢失 ) # 去除完全重复的记录 df_borrow = df_borrow.drop_duplicates() # 借书时间为空的记录无效,直接删除 df_borrow = df_borrow.dropna(subset=["借书时间"]) # 还书时间为空表示在借,标记为当前时间,后续计算借阅天数时使用 df_borrow.loc[df_borrow["还书时间"].isna(), "还书时间"] = pd.Timestamp.now()parse_dates参数告诉pandas哪些列需要按照时间类型解析,这样后续可以直接做日期加减,不需要再手动pd.to_datetime。dtype参数保证借阅证号以字符串形式读出,避免“000123”变成“123”。drop_duplicates()默认判断整行是否完全一致,对于续借场景下只有操作时间不同的记录不会误删。最后一步把空还书时间替换为当前时间,是为了让借阅天数变成正数,但这只在“分析时点”有效,如果数据会更新,建议额外保留一个“是否在借”标记列。
2.3 用merge把借阅、读者、图书三张表拼成宽表
单张流水表只能算借阅次数,想要按读者类型、图书分类分析,必须把三张表合并成宽表。常见做法是用pandas的merge函数,键分别取借阅证号和图书条码:
# 读取读者表和图书表,同样用string类型读取编码字段 df_reader = pd.read_excel("reader.xlsx", dtype={"借阅证号": str}) df_book = pd.read_excel("book.xlsx", dtype={"图书条码": str}) # 先合并读者信息,再合并图书信息 df = df_borrow.merge(df_reader, on="借阅证号", how="left") df = df.merge(df_book, on="图书条码", how="left") # 检查合并后数据是否完整 print(df.shape) print(df[["读者类型", "书名", "中图分类号"]].isna().sum())merge的on参数指定关联键,how="left"表示以借阅流水表为主体,左边表的每条记录都保留,右边表匹配不到的部分填充为NaN。合并完成后输出缺失值数量,如果“读者类型”缺失很多,说明借阅证号在两张表里格式不一致,最容易被忽略的就是证号前后的空格。这时可以用df["借阅证号"] = df["借阅证号"].str.strip()统一去除。合并后的宽表,每一行仍是一次借阅行为,但已经带上了读者和图书属性,后续分析不必反复查表。
3. 借阅行为分析:核心指标计算与读者分层
宽表准备好之后,下一步就是把“借阅记录”变成“行为指标”。图书馆借阅数据分析的核心不是看单条记录,而是看读者和图书在时间轴上的规律。我通常会从三个维度切入:借阅时长与周期、读者类型分组画像、热门图书与学科分布。
3.1 借阅时长、借阅周期和续借率:先算一组基础指标
一次借阅行为的质量,不只体现在借了没有,还体现在借了多久、有没有续借。代码里可以直接用时间列做差:
# 计算单次借阅天数 df["借阅天数"] = (df["还书时间"] - df["借书时间"]).dt.days # 出现负数说明还书时间早于借书时间,通常是录入错误,置为缺失值 df.loc[df["借阅天数"] < 0, "借阅天数"] = None # 统计每位读者的借阅总次数、平均借阅天数、续借次数列 borrow_stats = df.groupby("借阅证号").agg( 借阅次数=("借书时间", "count"), 平均借阅天数=("借阅天数", "mean"), 续借次数=("续借次数", "sum") ) # 续借率:续借总次数 / 借阅总次数,反映读者对图书的依赖程度 borrow_stats["续借率"] = borrow_stats["续借次数"] / borrow_stats["借阅次数"]这里用groupby().agg()一次算出多个聚合指标,列名直接作为Python标识符传入,可读性比agg({"列名": "sum"})更好。借阅天数取平均值前要留意空值,NaN不会参与计算,但如果某条记录借阅天数为0(当天借当天还),也会拉低均值。续借率数值通常在0到1之间,如果发现大于1,多半是因为同一本书被多次续借而次数统计口径不一致,需检查“续借次数”列单位。
3.2 按读者类型和学院分组的借阅画像
单个读者的指标波动大,按读者类型聚合后,才能看出规律。例如研究生和本科生的借阅习惯、热门学院的平均借阅量差异。代码如下:
# 按借阅月份和读者类型生成交叉统计 df["借阅月份"] = df["借书时间"].dt.to_period("M") reader_month = df.groupby(["读者类型", "借阅月份"]).size().unstack(fill_value=0) # 按学院统计人均借阅量 college_stats = df.groupby("所属学院").agg( 读者数=("借阅证号", "nunique"), 借阅总量=("借书时间", "count") ) college_stats["人均借阅量"] = college_stats["借阅总量"] / college_stats["读者数"] # 取借阅总量最高的前10个学院 print(college_stats.sort_values("借阅总量", ascending=False).head(10))dt.to_period("M")把时间转换为年月周期,这样按月份分组不会受到“天”的干扰。unstack()把读者类型从行索引变成列名,形成一张行是月份、列是读者类型的交叉表,适合后续绘图。学院统计里的“读者数”使用nunique而不是count,因为同一个读者可能借阅多次,nunique才能避免重复计数。人均借阅量这个指标很容易被总借阅量大但读者数多的学院稀释,所以前面必须同时计算两个基础值。
3.3 热门图书和中图分类号的借阅排行
热门图书榜是图书馆最看重的输出之一,但直接value_counts书名只能得到单册图书排行,无法回答“哪一类书借得多”。中图分类号的前一位或前两位代表学科大类,适合用来观察馆藏结构是否合理:
# 直接统计书名借阅次数 top_books = df["书名"].value_counts().head(20) # 提取中图分类号前两位作为学科大类 df["分类大类"] = df["中图分类号"].str[:2] cat_stats = df.groupby("分类大类").agg( 借阅量=("借书时间", "count"), 图书种类=("图书条码", "nunique") ).sort_values("借阅量", ascending=False) # 计算每类图书的平均单种借阅次数 cat_stats["单种均借量"] = cat_stats["借阅量"] / cat_stats["图书种类"]str[:2]对字符串列取前两个字符,例如TP311变成TP。如果原始字段里有形如“TP311.56”的带点写法,前两位提取仍然正确,但如果分类号以字母开头,个别条码写成小写,需要先统一str.upper()。单种均借量是一个容易被忽视的指标,它反映该类图书是否存在“少数几本被借爆,多数书无人问津”的现象,对采购决策比单纯借阅量更有参考价值。
下表展示分类统计结果的典型样式,实际运行时列名需与导出数据保持一致:
| 分类大类 | 借阅量 | 图书种类 | 单种均借量 |
|---|---|---|---|
| TP | 15200 | 320 | 47.5 |
| I | 9800 | 560 | 17.5 |
| O | 6400 | 410 | 15.6 |
4. 借阅趋势预测、RFM读者分级与协同过滤推荐
基础指标只能呈现历史规律,想要让分析“往前走一步”,需要引入建模环节。图书馆借阅数据分析里最常见的三类建模场景是:预测未来借阅量、给读者价值分级、向读者推荐可能感兴趣的图书。这三类场景都不需要深度学习,线性模型和经典协同过滤就足够。
4.1 用移动平均和线性回归预测借阅趋势
借阅量随学期呈周期性波动,直接对原始序列做线性回归会忽略寒暑假导致的低谷。我先用移动平均平滑序列,再对平滑后的趋势做短期预测:
# 按周统计借阅次数,并做4周中心移动平均 weekly = df.set_index("借书时间").resample("W").size() trend = weekly.rolling(4, center=True).mean() # 把趋势数据转换为线性回归需要的二维特征 y = trend.dropna() x = np.arange(len(y)).reshape(-1, 1) from sklearn.linear_model import LinearRegression model = LinearRegression() model.fit(x, y.values) # 预测未来4周的趋势值 future_x = np.arange(len(y), len(y) + 4).reshape(-1, 1) future_y = model.predict(future_x)resample("W")按周分组统计借阅次数,rolling(4, center=True).mean()计算四周中心移动平均,能有效抹平单周异常波动。使用线性回归预测趋势前,必须明确它只能捕捉整体线性方向,无法预测期末考试周这种突发性高峰。实际使用时,可以只把预测结果作为“业务基线”,当真实值显著高于预测值时,说明有临时性借阅活动发生。
4.2 把RFM模型改造成借阅场景的读者分级
RFM是电商客户分析的经典模型,对应最近购买时间、购买频率和购买金额。借阅场景下没有金额,我通常把“最近一次借阅时间”记为R,“借阅次数”记为F,“借阅图书种类”记为M,三者用分位数打分后进行等级划分:
# 构造RFM基础数据 rfm_data = df.groupby("借阅证号").agg( R=("借书时间", "max"), # 最近借书时间 F=("借书时间", "count"), # 借阅次数 M=("图书条码", "nunique") # 借阅过多少种书 ) # 计算最近一次借阅距离现在的天数 rfm_data["R"] = (pd.Timestamp.now() - rfm_data["R"]).dt.days # 用四分位数给三项指标打分,R越小越好,F和M越多越好 rfm_data["R_score"] = pd.qcut(rfm_data["R"], 4, labels=[4, 3, 2, 1]).astype(int) rfm_data["F_score"] = pd.qcut(rfm_data["F"].rank(method="first"), 4, labels=[1, 2, 3, 4]).astype(int) rfm_data["M_score"] = pd.qcut(rfm_data["M"].rank(method="first"), 4, labels=[1, 2, 3, 4]).astype(int)pd.qcut按分位数分成4等份,labels参数手动指定得分方向。R的分位数越大代表天数越多,所以给它反转的得分标签。F和M存在大量并列值,直接qcut会因为分位点重复而报错,先.rank(method="first")给并列值分配不重复的排名,再分箱就稳定了。RFM的结果不需要立刻定义复杂规则,我一般先按“R_score高且F_score高”视为活跃读者,“R_score低且F_score高”是潜在流失读者,再结合M_score判断高价值读者。
4.3 用物品协同过滤为“暂无借阅记录”的读者找书
图书馆借阅场景和电商不同,读者数量相对稳定,图书生命周期长,非常适合物品协同过滤。简化实现时不需要额外安装surprise库,直接构造用户-图书矩阵并用余弦相似度计算图书之间的相似度:
# 构造用户-图书矩阵,行是读者,列是书名,值为借阅次数 matrix = df.pivot_table( index="借阅证号", columns="书名", values="借阅次数", aggfunc="sum", fill_value=0 ) # 计算书名之间的余弦相似度 from sklearn.metrics.pairwise import cosine_similarity book_sim = pd.DataFrame( cosine_similarity(matrix.T), index=matrix.columns, columns=matrix.columns ) # 给某个读者推荐:找出他借过的书,按相似度加权汇总 reader_borrow = matrix[matrix.loc["A0001"] > 0] scores = book_sim[reader_borrow.index].sum(axis=1) scores = scores[matrix.loc["A0001"] == 0] # 排除已借过的书 print(scores.sort_values(ascending=False).head(10))pivot_table把借阅记录转为二维矩阵,缺失值填0。计算相似度时先对矩阵转置,因为cosine_similarity默认计算行与行之间的相似度,转置后每行才对应一本书。最后给读者推荐时,用他借过的所有书的相似度分数求和,排除已借书目后取最高的前十个。这个简化版没有去除流行度偏差,实际项目中可以在相似度汇总时对热门图书降权,例如乘以“平均借阅次数/该书借阅次数”的对数变换。
5. 从结果到报表:用matplotlib输出借阅分析图表与导出
分析模块写完后,整个项目最后一步是让管理员能直接看到结果。比起直接抛出一堆打印数据,我习惯把核心图表组合成一张总览图,并同时导出excel和html报表。
5.1 可视化布局:一图看懂核心借阅指标
借阅趋势、读者分布和热门图书适合放在同一张大图里。绘图前必须先设置中文字体,否则图例和标题会变成方块:
import matplotlib.pyplot as plt import matplotlib matplotlib.rcParams["font.sans-serif"] = ["SimHei"] # 设置中文字体 matplotlib.rcParams["axes.unicode_minus"] = False # 解决负号显示问题 plt.figure(figsize=(12, 8)) # 子图1:月度借阅趋势 plt.subplot(2, 2, 1) monthly = df.set_index("借书时间").resample("M").size() monthly.plot(color="#2E86AB") # 子图2:借阅量前10的学院 plt.subplot(2, 2, 2) college_top = college_stats["借阅总量"].sort_values(ascending=False).head(10) college_top.plot.bar() # 子图3:分类大类占比 plt.subplot(2, 2, 3) cat_stats.head(8)["借阅量"].plot.pie(autopct="%1.1f%%") plt.tight_layout() plt.savefig("library_analysis_report.png", dpi=150)subplot把画布切分成2行2列,前三个区域分别放趋势、学院柱状图和分类饼图。pandas的plot方法会自动使用DataFrame的索引作为横轴,所以月度趋势和学院的排序要在进入绘图前完成。plt.tight_layout()避免子图之间标签重叠,savefig导出高清png,方便直接放到PPT或打印。
5.2 把分析结果导出为excel和html报告
代码运行的最终产出应该是结构化的报告文件。我用ExcelWriter把多个统计结果写到不同sheet,同时用to_html生成一个浏览器可直接打开的简单报告目录:
with pd.ExcelWriter("借阅分析结果.xlsx") as writer: borrow_stats.to_excel(writer, sheet_name="读者统计") college_stats.to_excel(writer, sheet_name="学院统计") cat_stats.to_excel(writer, sheet_name="图书分类统计") # 生成一个简单的html表格页 stats_html = college_stats.to_html() with open("借阅分析报告.html", "w", encoding="utf-8") as f: f.write(f"<html><meta charset='utf-8'><body>{stats_html}</body></html>")ExcelWriter上下文管理器会自动关闭并保存文件,多个sheet之间互不影响。to_html把DataFrame转成html标签,配合encoding="utf-8"保证中文正常显示。这里最大的坑是文件路径包含中文,在部分windows环境下ExcelWriter会因为编码报错,建议代码开头加import sys; sys.stdout.reconfigure(encoding="utf-8"),或统一用纯英文文件名再手动改中文名。
本文还有配套的精品资源,点击获取