资讯动态

Python图书馆借阅数据分析:从数据清洗到排名可视化实战

发布时间:2026/10/14 12:02:04 来源:尧图企业网站定制
简介面向图书馆管理与数据分析初学者的完整实践资源聚焦基于Python的借阅数据清洗、挖掘与可视化。压缩包共16个文件包含4个Python分析脚本分别用于主题词提取、帕累托分析、热门图书统计与借阅排名变化7个Excel数据文件涵盖2014—2017年图书借还记录、读者信息及图书目录并附《中国图书馆图书分类法》简表和停用词表便于结合背景图进行可视化展示。资源约46.45MB结构紧凑、脚本与数据配套适合数据挖掘课程设计或图书馆运营分析参考。已有2275人学习下载可帮助读者快速复现借阅主题分布、TOP20图书排名变化等分析流程掌握从数据预处理到结论输出的完整项目思路。1. 基于Python的图书馆借阅数据分析这套资源能干什么、适合谁做python数据分析项目最怕的不是算法不会而是拿到真实数据后不知道怎么下手。这套图书馆借阅数据分析资源正好补上这一课它把2014到2017四年的借还流水、图书目录、读者信息都备齐了还配了四个直接能跑的Python脚本热门书TOP20统计、跨年排名变化、帕累托图生成、主题词提取。你不需要从零搭框架改一下文件路径就能看到完整的从数据清洗到分析出图的过程。适合正在做课设或毕设的数据挖掘新手也适合图书管理员用Excel之外的方式看馆藏热点。这篇笔记会按脚本逐个拆原理、参数和踩过的坑照着敲一遍你也能做出同样的效果。2. 数据预处理Excel读入、字段清洗与四年记录合并真实借阅数据下载下来不是直接能分析的至少有一半工作量在处理列名不统一、日期带格式、书名带空格这些脏数据。这一章先把预处理逻辑讲清楚因为后面所有统计都依赖这个基础。2.1 数据集构成与字段口径确认资源里除了四个Python脚本配套数据主要有这些图书借还2014.xlsx到图书借还2017.xlsx是四年借还流水图书目录.xlsx是馆藏书目读者信息.xlsx是读者维度信息还有stopwords.txt、library_new_words.txt、《中国图书馆图书分类法》简表.txt用于主题词提取和分类归并。建议拿到数据后第一件事不是写代码而是用Excel或pandas把每个文件打开看几行确认列名。借还流水里通常有读者条码、书名、借书日期、还书日期、馆藏地等列。不同年份的文件列名可能不一致比如有的叫“读者证号”有的叫“读者条码”直接纵向拼接就会对不上。所以预处理第一步是建立列名映射把可能的说法统一成英文变量名后面写统计逻辑才不用反复回头看原始列名。用pandas读入时可以顺便做两件小事一是把条码这类标识列用dtypestr强制转成字符串避免超过15位时被Excel科学计数法截断二是只读前几行确认结构。这里给出一个通用检查写法import pandas as pd df pd.read_excel(data/图书借还2014.xlsx, nrows5, dtype{读者条码: str}) print(df.columns.tolist()) print(df.head())nrows5只加载前5行速度快适合先摸字段dtype参数是对单列指定解析类型。如果列名是“读者证号”而不是“读者条码”说明文件的字段和你预期不一致需要调整后面的映射字典。2.2 批量读取四年Excel并统一列名四个年度的文件结构基本相似写法上不要复制四遍用循环加字典一次性处理。我一般把列名映射单独放在一个字典里这样以后遇到别的图书馆数据只需要改这个字典就能复用脚本。import pandas as pd files { 2014: data/图书借还2014.xlsx, 2015: data/图书借还2015.xlsx, 2016: data/图书借还2016.xlsx, 2017: data/图书借还2017.xlsx, } col_map { 读者条码: reader_id, 读者证号: reader_id, 书名: book_name, 题名: book_name, 借书日期: borrow_date, 借出日期: borrow_date, 还书日期: return_date, 归还日期: return_date, 馆藏地: location, } df_list [] for year, path in files.items(): df pd.read_excel(path, dtype{读者条码: str}) df df.rename(columnscol_map) if book_name not in df.columns: raise ValueError(f{year} 年数据缺少书名列请检查 col_map) df[year] year df_list.append(df) all_borrow pd.concat(df_list, ignore_indexTrue) print(all_borrow.shape) print(all_borrow[year].value_counts())rename会把能匹配到的列名统一替换掉匹配不到的保留原样。if book_name not in df.columns是一个保险如果某年文件名格式不一样立刻报错而不是等统计时莫名报错。df[year] year给每条记录打上年份标签这是后面做跨年排名变化的依据。pd.concat默认纵向合并ignore_indexTrue避免索引重复。2.3 日期、空值、重复记录处理借阅分析最核心的字段是“借书日期”很多统计都按月、按季度走。真实Excel里日期可能是2015/1/3也可能是2015年1月3日直接用字符串比较一定会出问题。所以这里统一用pd.to_datetime转成真正的日期类型解析不了的就变成NaT。all_borrow[borrow_date] pd.to_datetime(all_borrow[borrow_date], errorscoerce) all_borrow[return_date] pd.to_datetime(all_borrow[return_date], errorscoerce) invalid_date all_borrow[borrow_date].isna() print(f借阅日期解析失败: {invalid_date.sum()} 行) all_borrow all_borrow[~invalid_date] all_borrow all_borrow.drop_duplicates() all_borrow[book_name] ( all_borrow[book_name] .astype(str) .str.replace(\u3000, , regexFalse) .str.strip() )errorscoerce遇到无法解析的日期会置为NaT不会中断程序。isna()统计失败行数如果失败比例超过1%就要去看原始日期格式常见问题是有些行混入了文本说明比如“已还”两个字。drop_duplicates()默认对整行去重能去掉完全重复的录入但要注意如果同一个人同一天借了同一本书的上下册它们不重复后面的统计会各自计数这个问题在避坑章节再展开。书名里的全角空格\u3000和普通空格肉眼看不出来排序时却会让同一本书被拆成两个所以先用replace把全角空格换成半角再用strip去掉首尾空白。3. 热门书TOP20与跨年排名变化从统计口径到结果落表资源里最有辨识度的输出文件是“借书名TOP202014-2017排名变化.xlsx”这其实就是图书馆关心的“哪些书一直热门哪些书在没落”。要把这个表做出来第一步是确定统计口径第二步才是写代码。3.1 热门书TOP20按书名聚合还是按“书”聚合常见统计口径是“一条借阅记录算一次借出”也就是直接把书名分组计数。但实际业务里同一本书可能有多个复本读者借了其中一册记录里的书名完全相同所以按书名聚合得到的是“该书名被借了多少次”这正好是热门书分析需要的。如果目录里有ISBN字段理论上应该按ISBN精确到版本但很多老系统同一本书不同版本的书名一样、ISBN不同会分别计数。资源里的统计脚本是按书名来的我一般也建议保持这个口径——因为借阅者选书时甚至分不清版次按书名统计更贴合“热门程度”这个概念。hot ( all_borrow.groupby(book_name) .size() .reset_index(nameborrow_count) .sort_values(borrow_count, ascendingFalse) ) top20 hot.head(20).copy() top20[rank] range(1, 21) print(top20[[rank, book_name, borrow_count]])groupby(book_name).size()计算每个书名出现的行数等价于借阅次数。reset_index(nameborrow_count)把分组索引变成普通列同时给计数列起名。sort_values按借阅次数降序排列。head(20)取前20再手动加一个rank排名列。这里有个容易被忽视的细节如果all_borrow是经过drop_duplicates()的同一条记录不会重复计数但“续借”如果会被系统记录为一条新流水就会高估借阅热度。真遇到这种情况我一般会先按“读者编号 书名 借书日期”去重只保留同一个人同一天借同一本书的第一条。3.2 生成“2014-2017排名变化”表格外连接避坑排名变化表要做成什么样我的做法是行是书名列是2014年排名、2015年排名、2016年排名、2017年排名再加一列“排名变化”正数代表排名上升负数代表下降。这样一眼就能看出趋势。难点在于不是每本书四年都进TOP20某本书2014年排第32015年掉出前20那2015年的排名怎么填直接填0约定0代表“当年未进TOP20”。各年数据合并时要用外连接否则只保留四年都在榜的书信息就丢了。yearly_top {} for year in [2014, 2015, 2016, 2017]: df_year all_borrow[all_borrow[year] year] counts ( df_year.groupby(book_name) .size() .sort_values(ascendingFalse) ) top_n counts.head(20).reset_index(namecount) top_n[rank] range(1, 21) yearly_top[year] top_n.set_index(book_name)[rank].rename(f{year}_rank) rank_table pd.concat(yearly_top.values(), axis1, joinouter) rank_table rank_table.fillna(0).astype(int) rank_table[trend_2014_2017] rank_table[2014_rank] - rank_table[2017_rank] rank_table rank_table.sort_values(2017_rank) print(rank_table.head(10))这里的关键是set_index(book_name)[rank]它生成一个“书名为索引排名为值”的Series。pd.concat(..., axis1, joinouter)把四个Series按书名做外连接某年没进TOP20的书在该列就是NaN再用fillna(0)填充。trend_2014_2017的计算是“2014排名减2017排名”比如从第5名变成第2名5 - 2 3正数说明热度上升但要注意方向如果写反就会得出完全相反的结论。所以我建议生成表后人工抽查几本已知的书确认排名列和趋势列方向一致。3.3 把排名变化表写入Excel并加简单格式脚本的落盘用的是pd.ExcelWriter它能在一个Excel文件里写多个Sheet。排名变化表放第一页累计热门详情放第二页方便直接拿去汇报。with pd.ExcelWriter(借书名TOP20-2014-2017排名变化.xlsx, engineopenpyxl) as writer: rank_table.to_excel(writer, sheet_name排名变化) hot.head(50).to_excel(writer, sheet_name累计热门, indexFalse) ws writer.sheets[排名变化] ws.column_dimensions[A].width 40 ws.freeze_panes B2engineopenpyxl是写xlsx的标准引擎。freeze_panesB2让第一行和第一列始终可见数据列很多时不会滚动丢失列名。column_dimensions[A].width设置第一列宽度因为书名一般比较长。如果想突出TOP1可以用openpyxl的PatternFill给单元格加背景色不过脚本里没做那么复杂保持数据本身清晰就够了。4. 帕累托分析与主题词提取把借阅规律变成图表和关键词热门书TOP20只能回答“哪几本书借得多”图书馆还关心“这20本书占总借阅量多大比例”“读者都在关注什么主题”。这两个需求分别对应生成帕累托和提取主题词两个脚本。4.1 帕累托图累计占比80%的书有多少帕累托分析又叫二八定律分析核心是算“借阅次数排名前n的书累计贡献了多少借阅量”。如果前10%的书贡献了80%的借阅量说明馆藏使用高度集中如果曲线很平说明读者兴趣分散可以根据这个决定采购结构。import matplotlib import matplotlib.pyplot as plt matplotlib.rcParams[font.sans-serif] [SimHei] matplotlib.rcParams[axes.unicode_minus] False counts all_borrow[book_name].value_counts().values cumsum counts.cumsum() total counts.sum() cum_ratio cumsum / total plot_n 50 x range(1, plot_n 1) fig, ax1 plt.subplots(figsize(10, 6)) ax1.bar(x, counts[:plot_n], color#4C72B0, label借阅次数) ax2 ax1.twinx() ax2.plot(x, cum_ratio[:plot_n], color#C44E52, markero, label累计占比) ax2.axhline(y0.8, colorgray, linestyle--, label80%线) ax1.set_xlabel(书排名) ax1.set_ylabel(借阅次数) ax2.set_ylabel(累计占比) plt.title(借阅帕累托分析) plt.tight_layout() plt.savefig(pareto.png, dpi150)value_counts()已经返回按次数降序排列的Seriesvalues直接拿次数数组。cumsum()计算累计借阅次数除以total得到累计占比。twinx()创建一个共享x轴但独立y轴的双轴图柱状图表示每本书的借阅次数折线图表示累计占比。axhline画一条80%参考线可以直接看出大概前多少本跨过了这条线。这里有个细节counts是按“精确书名”统计的如果同一本书因为版本不同被拆开帕累托曲线会显得更分散。所以我通常会在跑这个脚本前先把书名里明显的版本信息比如“上册”“精装”去掉再做聚合让结论更贴近读者视角。4.2 主题词提取自定义词典与停用词主题词提取脚本用的是jieba分词配套文件里的library_new_words.txt是图书馆场景自定义词典stopwords.txt是停用词表。分词的目的是从书名里提炼高频词比如“数据分析”“Python”“管理学”从而判断馆藏热点集中在哪些知识领域。import jieba stop_words set() with open(stopwords.txt, encodingutf-8) as f: for line in f: w line.strip() if w: stop_words.add(w) jieba.load_userdict(library_new_words.txt) word_freq {} for book in all_borrow[book_name].drop_duplicates(): words jieba.lcut(book) for w in words: w w.strip() if not w or w in stop_words or len(w) 2: continue word_freq[w] word_freq.get(w, 0) 1 top_words sorted(word_freq.items(), keylambda x: x[1], reverseTrue)[:50] for word, freq in top_words[:20]: print(word, freq)stop_words用set存储查询复杂度O(1)。load_userdict把自定义词表加载进jieba这样“大数据”“人工智能”这类词不会被拆成“大”“数据”。分词后先strip()去掉词两侧空白再过滤停用词和单字词。len(w) 2排除“的”“了”“书”等无意义词。这里的统计单位需要想清楚脚本遍历的是drop_duplicates()后的唯一书名所以word_freq[w]统计的是“包含这个词的书名数量”。如果想按借阅次数加权应该改成遍历原始all_borrow每出现一条借阅记录就加一次那样高频书的主题词会明显占优。两种口径都有道理我一般会两种都看一遍加权结果更贴近读者真实需求去重结果更贴近馆藏覆盖面。4.3 结合中图法简表做分类归并借阅分析做到后面常常要看“大类热度”——计算机类借得多还是文学类借得多。中图法简表文件提供了分类号前缀到大类名的映射但前提是借阅记录或图书目录里有索书号。资源里图书目录.xlsx应该承担这个作用。catalog_df pd.read_excel(图书目录.xlsx) print(catalog_df.columns.tolist()) if 索书号 in catalog_df.columns: catalog_df[catalog_code] catalog_df[索书号].astype(str).str[0] else: catalog_df[catalog_code] Z book_catalog dict(zip(catalog_df[书名], catalog_df[catalog_code])) all_borrow[catalog_code] all_borrow[book_name].map(book_catalog).fillna(Z)取索书号第一个字符作为中图法大类代码比如TP312取T代表工业技术I247取I代表文学。dict(zip(...))把书名映射到分类代码map按书名匹配没匹配上的用Z兜底。这里要特别注意如果图书目录里的书名和借阅记录里的书名有细微差别比如多了副题名map就会失败。所以我一般会先把两边书名都做同样的清洗去空格、统一括号再执行映射。匹配完成后用groupby(catalog_code).size()就能得到各大类的借阅次数分布还可以进一步结合简表把代码转成中文大类名。5. 常见问题排查五个最容易翻车的细节做这套分析时我踩过不少坑有些问题一眼就能看出来有些会潜伏到绘图或落表阶段才爆发。这里挑五个典型记录每条按“现象→原因→解决”写清楚。5.1 日期列读出全是字符串按月统计错乱现象用pd.to_datetime转换后部分日期变成了NaT按月聚合出现很多NaN甚至报错“datetime64类型不支持”。原因Excel列里混着“2015-01-02”“2015/1/2”“2015年1月2日”多种格式或者有些单元格是文本。pd.to_datetime默认解析常见格式遇到混合格式时会一部分成功一部分失败失败的变成NaT。解决先打印失败样本看具体是不规范文本还是时间格式问题。如果是文本混入用errorscoerce配合dropna删掉如果是格式不统一把Excel原文件另存为CSV后用encodingutf-8读再指定format参数。我自己的习惯是读取后第一时间对日期列做类型检查all_borrow[borrow_date].dtype必须是datetime64[ns]才算过。5.2 书名里的隐藏空格导致同一本书被拆成两条现象热门书TOP20里出现“平凡的世界”和“平凡的世界 ”末尾多一个空格同时上榜两个名次都不高合并后却可能进前五。原因Excel录入时有人名带了全角空格或者末尾回车strip()只去半角空格全角空格\u3000没被处理。解决清洗书名时先str.replace(\u3000, , regexFalse)把全角空格替换成半角再做strip()。更稳妥的做法是把所有空白字符统一成一个半角空格可以用re.sub(r\s, , book_name)。正规项目里我会在清洗后输出一列“清洗后书名”和原书名对比确认没有误合并。5.3 停用词表没生效主题词全是“的”“了”“书”现象跑主题词提取输出前20全是“的”“了”“在”“和”自定义词典也没起作用。原因停用词文件可能是GBK编码而读文件用的encodingutf-8导致内容变成乱码或空也可能停用词表里本身有空格line.strip()后变成空字符串被跳过。另外jieba.load_userdict必须在第一次分词前调用如果前面代码已经触发过分词词典就加载不进去。解决读停用词时用encodinggbk或errorsignore先打印前几行确认能用。调用jieba.load_userdict前不要执行任何jieba.lcut操作。还要注意停用词表里的词可能带“\n”以外的说明逐行去掉空白符后过滤空字符串。5.4 matplotlib中文显示方块现象帕累托图生成后标题和图例里的中文全是方框英文正常。原因当前环境里matplotlib默认字体不支持中文需要手动指定系统里有的中文字体。Windows一般是SimHei或Microsoft YaHeiLinux下可能是WenQuanYi Zen Hei老代码里只设了SimHei换机就失效。解决在绘图代码开头统一设置import matplotlib matplotlib.rcParams[font.sans-serif] [SimHei, Microsoft YaHei, WenQuanYi Zen Hei] matplotlib.rcParams[axes.unicode_minus] Falseaxes.unicode_minus不加的话y轴负号会显示成乱码。如果这些字体都没有就用中文字体文件路径注册或者干脆用英文标签但注意如果脚本里标题没写英文还是要注册的。5.5 排名变化表里某年排名全是0趋势列算反现象2014年进TOP20的书在2015年“排名”为0一看全表大量0trend_2014_2017列正负方向跟人工查阅结果相反。原因pd.concat外连接后某年没进TOP20的位置是NaN直接fillna(0)没问题但趋势列如果用“2017排名减2014排名”0参与了运算——比如2017年没进TOP20的书0 - 5 -5看上去是下降5名实际上它是跌出榜单不能和“第8名变第3名”直接相减。方向看反是因为没有固定参照年。解决老规矩是先把0定义成“未入榜”不与具体名次混算趋势列只对两年都在榜的书计算未入榜的统一标成None或字符串“跌出”。计算方向前先写一行注释# 正数代表排名上升靠前再检查一两个已知案例。那以后我再排这个表会先打印出“2014第1、2017第1、2014第1、2017第20”这几行确认逻辑没反。6. 把分析流程串成自动报表命令行参数与结果抽查四个脚本单独跑没问题但每次换数据都要改文件路径、改年份范围很不方便。我的做法是写一个主流程脚本用命令行参数控制输入目录和输出文件名这样以后面对新一年的数据不用改任何代码。进阶版本的核心是用argparse接收参数把前面几章的代码块封装成函数最后统一调用。import argparse def main(): parser argparse.ArgumentParser(description图书馆借阅数据分析) parser.add_argument(--data_dir, defaultdata, helpExcel文件所在目录) parser.add_argument(--output, defaultanalysis_result.xlsx, help输出Excel文件名) parser.add_argument(--years, default2014,2015,2016,2017, help要分析的年份逗号分隔) args parser.parse_args() years [int(y) for y in args.years.split(,)] # 读数据、清洗、统计、画图、写Excel的流程依次执行 print(f分析年份: {years}) print(f结果输出到: {args.output}) if __name__ __main__: main()这样跑一条命令就能完整走完流程先读数据再清洗合并接着算热门书、排名变化、帕累托图和主题词最后把Excel表格和PNG图都生成到指定目录。我习惯在最后一步加一个“结果抽查”逻辑随机抽100条原始借阅记录人工数一遍哪本书借得最多和TOP20的榜首比对。这个动作看起来原始却能在数据清洗出错时第一时间发现问题。有一次就是因为上下册被合并错榜首书完全不对抽查帮我挡下了那版结论。从那以后我每次跑完这套分析都强制走一遍抽查再开始写汇报文字。数据分析到后面拼的不是模型多炫而是数据清洗够不够稳边界情况有没有都验证过。希望这个资源能帮你在图书馆借阅数据上少走几条弯路。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑