
简介这是一份面向Python数据可视化课程设计、期末大作业及毕业设计场景的完整项目资源以数字唐诗为主题完整覆盖数据清洗、统计分析、可视化图表呈现等主要环节整体难度适中适合具备基础Python知识、希望获得可运行参考项目的学生使用。所有源码已在本地编译通过并可正常运行评审成绩达到98分同时配有文档说明和答辩PPT便于理解项目结构、设计逻辑与展示要点也可直接作为课程答辩的演示材料。压缩包大小约42.39MB主要文件类型包括Python源码、说明文档及PPT演示文稿内容组织紧凑下载解压后即可对照学习。目前已有493人学习下载资源经助教老师审定内容规范且质量可靠对准备相关课程作业、期末大作业或毕业设计的学生具有较高的参考价值与实践意义。1. 数字唐诗大作业的核心是数据链不只是图表很多同学把数据可视化大作业理解成“把图表画得越花哨越好”于是答辩前一周疯狂堆样式、换主题色结果老师一句“这张图的统计口径是什么”“清洗前后差了多少行”就卡住了。数字唐诗这个选题恰恰最能暴露这个问题题目看上去文艺可底层是文本数据工程。基于 Python 实现的数字唐诗可视化项目从结构上把整条链路拆成了三层Pandas 清洗与标准化、jieba 词频统计与分析、PyEcharts 图表渲染与页面组装。源码里同时包含文档说明和答辩 PPT本地可以直接运行把数据路径换成自己的语料就能当作期末大作业、毕业设计或者课程设计的脚手架来用。准确说它把文化类数据可视化里最容易模糊的统计口径问题用工程方式固定下来了。2. 从原始文本到 DataFrame数据清洗的三道关卡2.1 先做语料探查再设计工程结构数字唐诗的源数据最常见的是两种格式一种是整体 JSON 文件每条记录包含author、title、paragraphs另一种是 CSV一行对应一首诗列为作者、诗题与正文。拿到源码后的第一步不是改图表而是先确认语料规模和数据边界。import pandas as pd df pd.read_csv(data/tang_poems.csv) print(原始形状:, df.shape) print(df.dtypes) print(df.head(3))这一段探查代码看起来简单实际能暴露大部分问题paragraphs字段可能是字符串形态的列表作者字段混有NULL或者“佚名”诗题经常带“其一”“并序”这种卷次标记。这三个问题如果不处理后面groupby会出现大量“未知作者”行字符长度统计也会因为控制字符而虚高。我一般会在洗完数据之后单独落一份干净的中间表而不是在原始 DataFrame 上反复改df_clean df.dropna(subset[author, title]) df_clean.to_csv(output/tang_poems_clean.csv, indexFalse, encodingutf-8-sig) print(清洗后形状:, df_clean.shape)这里的关键是让“清洗前形状”和“清洗后形状”出现在终端或者 PPT 里。数字人文类项目最怕的就是“说不清数据来源”数据量前后对比是最直接的证据。少了这一步后面的图表再漂亮也经不起追问。2.2 文本去噪用一条正则替代三段 if古诗词数据的真正坑不是缺值而是肉眼看不见的字符。全角空格\u3000、从网页复制来的不可见控制字符、诗题末尾的卷次标记都会在统计时产生干扰。常见做法是写一个带约束的清洗函数一次性处理import re def clean_text(s): if not isinstance(s, str): return # 移除全角空格与常见控制字符 s re.sub(r[\u3000\x00-\x08\x0b\x0c\x0e-\x1f], , s) # 去掉诗题常见后缀其一、其二、并序、有序 s re.sub(r(其[一二三四五六七八九十百]|并序|有序)$, , s) s s.replace( , ) return s.strip()逐段拆开说明\u3000是全角空格\x00-\x08和\x0e-\x1f覆盖大多数控制字符第二个正则把诗题末尾的“其一”“并序”这类卷次标记删除最后去掉半角空格。注意不要用单纯的str.strip()替代因为全角空格和换行符在strip()里未必能处理干净卷次标记更不在它的作用范围内。补充一个很容易被忽略的点如果语料是 JSON 形态paragraphs常常是数组而不是字符串要先转换再清洗。df[content] df[paragraphs].map( lambda x: .join(x) if isinstance(x, list) else str(x) )不转换的话后面所有正则表达式都会作用在数组的字符串形式上切出来的结果完全不可用。2.3 用“宽度”和“分段”建立统计维度清洗之后花几分钟把列级统计维度建出来。数字唐诗最常用的几个维度是作者、朝代、诗作长度、句子数量。它们都来源于原始字段的衍生计算。维度计算字段可视化用途作者author柱状图 Top15 诗人朝代era盛唐/中唐/晚唐分布饼图长度char_len直方图呈现五言/七言差异分段sentence_count古诗与绝句的粗略分类df[char_len] df[content].map(lambda s: len(s)) df[sentence_count] df[content].str.split(|。|||).apply(len) df[era] df[author].map(era_map).fillna(待考)朝代映射这里不需要引入任何依赖用字典硬编码即可把初唐、盛唐、中唐、晚唐四个时段对应到诗人列表。这个era列在后面几乎所有图表里都会用到比如“盛唐诗人作品量为什么高”这类问题直接用df[df[era] 盛唐]就能复现而不是在答辩现场临时改代码。工程纪律是所有图表都不要在原始 DataFrame 上直接筛选而是基于output/tang_poems_clean.csv这一份中间结果。这样被追问时可以当面导入文件逐步复现而不是在内存缓存里找数据这一点在课堂答辩场景里非常加分。3. 词频分析的正确打开方式自定义词表驱动 jieba3.1 原样分词在古诗词场景必然失控如果直接对“床前明月光”执行jieba.lcut输出往往是“床前 明月光”表面看没问题。但当整首《静夜思》放进去问题就出现了“疑是地上霜”可能被切成“疑/是/地上/霜”“低头思故乡”也可能切出“思故乡”或者“故乡”。现代汉语词库之外的组合会在统计结果里制造出大量噪声。唐诗还有个特殊现象高频单字大量出现“风、月、花、山、云”这类意象词经常以单字形态承载语义。默认分词器对单字的保留并不稳定再加上现代功能词“的、了、是”的干扰直接全量分词得到的 Top20 往往被无意义词占掉三分之一。这个现象几乎必然在答辩时被问到“你统计的‘月’跟网上的公开结果为什么对不上”答案的关键在自定义词典和停用词表上。3.2 用自定义词典和停用词控制 jieba 的输出粒度把词表拆成两个纯文本文件放在data/目录下。“数字唐诗”这类项目自定义词典至少包含诗人名、常用意象单字、常用双字词。data/poetry_dict.txt内容示例长安 明月 白云 洛阳 王维 杜甫 李白data/stopwords.txt内容示例之 乎 者 也 矣 焉 而 以然后封装统一的切词函数import jieba from collections import Counter jieba.load_userdict(data/poetry_dict.txt) STOPWORDS set(open(data/stopwords.txt, encodingutf-8).read().split()) def tokenize(text): words jieba.lcut(text, cut_allFalse, HMMFalse) return [w for w in words if w not in STOPWORDS and len(w) 1]关键参数解释如下cut_allFalse是精确模式保证输出不带冗余分词组合HMMFalse关闭隐马尔可夫新词发现这是控制粒度最重要的一步——唐诗单篇文本很短不适合依赖 HMM 去猜未登录词不然“明月光”会被继续拆成“明月/光”甚至“明/月光”。自定义词典里每行一个词不需要带词性load_userdict会让“长安、明月”这类多字词被完整保留。分词配置“床前明月光”输出问题默认词典 HMMTrue床前/明月光未登录词边界不稳定默认词典 HMMFalse床前/明月/光“明月”被拆散自定义词典 HMMFalse床前/明月光多字意象词完整保留再补一句jieba.setLogLevel(20)可以关闭 jieba 加载词库时的日志输出答辩现场终端会干净很多。这个小参数不值得写进文档但演示的时候很起作用。3.3 用 Counter 汇总 TopN 并落盘成标准表格统计阶段只需要一个循环和Countercounter Counter() for row in df[content]: counter.update(tokenize(row)) top_n counter.most_common(20) freq_df pd.DataFrame(top_n, columns[word, count]) freq_df.to_csv(output/top_words.csv, indexFalse, encodingutf-8-sig) print(freq_df)most_common(20)返回有序的二元组列表直接转 DataFrame 后第 4 章的词云和柱状图都从这个 CSV 读取保证口径一致。此时你可能发现 Top1 是“风”或者“月”这取决于语料规模和清洗策略如果加载了自定义词典但没加载停用词表结果里就会出现“之”“乎”这类虚词视觉上非常简陋。需要注意一个细节作者名是否进入自定义词典会直接影响桑基图的数据源。虽然关联图里作者字段是从原表读取的不会因分词被拆散但如果展示的高频词列表里出现“李/白”而不是“李白”老师就会怀疑整条分词链路的可信度。测试词表是否生效最快的方法是打印jieba.lcut(李白乘舟将欲行)看输出是不是“李白/乘/舟/将/欲行”。4. 用 PyEcharts 把统计结果变成可以演示的报告页面4.1 项目选型为什么用 PyEcharts 而不是 Matplotlib期末答辩场景下Matplotlib 有两个短板一是静态 PNG 无法在演示时交互二是中文渲染依赖系统字体换一台电脑展示就可能乱码。PyEcharts 基于 ECharts 的 JavaScript 渲染Python 端只负责把数据序列化输出为 HTML演示时用浏览器打开悬浮提示、缩放、数据刷选全部自带且不依赖网络环境。对比项MatplotlibPyEcharts渲染方式静态图片浏览器 HTML/JavaScript答辩现场互动需重新生成图片鼠标悬浮、缩放、切换都在页面内中文渲染依赖系统字体浏览器字体渲染稳定交付形态PNG/SVGHTML可直接嵌入超链接或独立打开从代码可读性角度PyEcharts 的链式调用也让组员之间 review 更容易每个配置项都落在opts命名空间里不需要像 Matplotlib 那样记忆大量底层 API。如果你做的是“期末大作业”而非生产级报表这个选型可以把开发时间压缩到一天以内。4.2 三张核心图表的参数拆解柱状图负责展示作者维度的数量分布。以 Top15 诗人为例from pyecharts.charts import Bar from pyecharts import options as opts bar_df pd.read_csv(output/author_stats.csv).sort_values(count, ascendingFalse).head(15) bar ( Bar(init_optsopts.InitOpts(width960px, height540px, themewesteros)) .add_xaxis(bar_df[author].tolist()) .add_yaxis( 作品数, bar_df[count].tolist(), itemstyle_optsopts.ItemStyleOpts(color#c07a53), ) .set_global_opts( title_optsopts.TitleOpts(title唐诗数量 Top15 作者), xaxis_optsopts.AxisOpts(axislabel_optsopts.LabelOpts(rotate35)), datazoom_opts[opts.DataZoomOpts(range_start0, range_end100)], ) ) bar.render(output/bar_top_authors.html)这里有两个参数对答辩效果影响很大rotate35让横轴作者名斜排避免 15 个名字挤在一起重叠datazoom_opts在图表底部生成一个缩放条演示时可以直接拖拽聚焦某一段区间比静态图多一层交互感。themewesteros是 PyEcharts 内置主题深色底配古诗词题材比较协调如果演示环境光线强换成浅色主题更稳妥。词云是数字人文类项目的标配from pyecharts.charts import WordCloud wc WordCloud() wc.add( series_name唐诗高频词, data_pairfreq_df[[word, count]].values.tolist(), word_size_range[12, 80], shapediamond, textstyle_optsopts.TextStyleOpts(font_familyNoto Serif SC), ) wc.render(output/wordcloud.html)data_pair必须是二维 list形如[[风, 2647], [月, 2418]]直接从前面落盘的 CSV 转换word_size_range控制词的大小区间数值跨度越大视觉冲击越强但同时会牺牲小词的可读性shape支持circle、diamond、triangle、pin唐诗题材建议用diamond不至于太卡通。桑基图是这份项目里最有区分度的一张图它把“作者”和“高频意象”两层数据用权重连接起来from pyecharts.charts import Sankey nodes [{name: poet} for poet in top_poets] nodes [{name: word} for word in top_words] links [] for poet, word, weight in relation_data: links.append({source: poet, target: word, value: weight}) sankey ( Sankey() .add( 作者-意象关联, nodes, links, linestyle_optopts.LineStyleOpts(opacity0.25, curve0.5), label_optsopts.LabelOpts(positionright), ) .set_global_opts(title_optsopts.TitleOpts(title诗人-高频意象关联)) ) sankey.render(output/sankey_author_word.html)节点列表需要保证每个source和target都在nodes里否则渲染时会出现悬空边。value表示该作者与某意象共同出现的次数统计逻辑通常是如果同一首诗里既出现“李白”又出现“月”则权重加一。curve0.5控制连线弯曲程度值越大弧线越明显数据量大的时候建议调到 0.2 以下否则整张图会糊成一团。4.3 页面组装让图表之间形成数据联动单张图各自打开没问题但答辩演示最好是一个入口讲完所有内容。PyEcharts 提供了Page组件把多张图按顺序拼到一个 HTML 里from pyecharts.charts import Page page Page(layoutPage.SimplePageLayout) page.add(bar) page.add(wc) page.add(sankey) page.render(output/digital_tang_report.html)Page.SimplePageLayout按添加顺序纵向堆叠适合答辩场景如果图表数量超过四张换成Page.DraggablePageLayout可以拖动排列但会多出保存布局的步骤。组装完成后做一次全局检查打开 HTML确认每张图的标题、坐标轴单位、数据跨度是否符合预期。常见问题包括柱状图横轴标签被截断、词云文本乱码、桑基图节点太多导致连线密集到不可读。这些都可以在 render 之前通过控制 TopN 数量来规避。5. 答辩演示里真正决定成绩的六个细节答辩 PPT 和源码本身只占一半另一半在于演示时的工程细节。以下六个是我复盘了多场课程答辩后认为最实际的加分点。细节一用“清洗前后差了多少行”开场。把清洗脚本的日志截图放进 PPT直接说“原始语料 4.8 万行清洗后保留 3.9 万行可用数据”。这句话传递的信息量远大于任何自我介绍它证明你处理过脏数据。细节二PPT 每页只回答一个问题。“这张图说明盛唐时期作品数量与长诗比例的关系”这就是给图配的第一句话。不要把柱状图、词云、桑基图挤在同一页信息密度过高时提问环节很难聚焦。细节三固定一条三分钟演示路径。终端运行python run.py打开digital_tang_report.html按“柱状图 → 词云 → 桑基图”顺序讲完数据闭环剩余时间留给提问。反复练习这条路径确保每一步在三十秒内完成。细节四把自定义词典和停用词表写进附录。应对“你的结果为什么跟网上不一样”这个问题时当场翻到附录页指着data/poetry_dict.txt说明哪些词被强制保留、哪些虚词被过滤。这比口头解释有说服力。细节五预演异常情况。图表空白页是换代机器最常见的故障多半是 PyEcharts 版本不一致或 CSV 路径失效。PPT 里放一张静态结果图兜底同时在演示机上提前执行一次pip install -r requirements.txt这能规避八成环境问题。细节六把三个数字放在答辩桌面上。清洗前规模、清洗后规模、Top1 关键词出现次数。这组数字是一根承重链无论老师追问数据来源、统计口径还是图表选型你都可以把话题引回这条链路上证明项目的每一层都是可复现的而不是临时画出来的结果图。本文还有配套的精品资源点击获取