• NLP 实战 (9) | CSDN topN指数月排行榜竞赛动画


    开源一个 topn 词竞赛动画项目 topn_race:

    核心功能:

    • 输入:按月统计的topN词频数据
    • 输出:topN词频竞赛动画(可带音效)

    源码结构

    本项目基于开源项目:https://github.com/dexplo/bar_chart_race 定制,src/bar_chart_race 从 bar_chart_race 项目的源代码修改以适配需求。

    依赖库:

    progress==1.5
    matplotlib==3.4.2
    pandas==1.2.4
    numpy==1.19.5
    moviepy==1.0.3
    
    • 1
    • 2
    • 3
    • 4
    • 5

    源码结构:

    .
    ├── LICENSE
    ├── README.md
    ├── data
    │   ├── csdn_ask_top10_month
    │   │   ├── 2008-05-01.json
    │   │   ├── 2008-06-01.json
    │   │   ├── ...
    │   └── csdn_trends_top10_month
    │       └── csdn_index_top_10.csv
    ├── demo
    │   ├── csdn_ask_top10_month.gif
    │   ├── csdn_trends_top10_month.gif
    │   └── demo.md
    ├── main.py
    ├── pub
    │   ├── ...
    ├── requirements.txt
    └── src
        ├── bar_chart_race
        │   ├── __init__.py
        │   ├── chart.py
        │   └── colormaps.py
        ├── common
        │   ├── __init__.py
        │   ├── error.py
        │   ├── gif.py
        │   ├── json.py
        │   ├── path.py
        │   ├── random.py
        │   └── utils.py
        └── top.py
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    其中:

    • main.py 是测试程序入口
    • src/top.py 是 topN 竞赛动画的逻辑组织控制层
    • src/common 提供了一些基本的utils
    • src/bar_chart_race 从 bar_chart_race 项目的源代码修改以适配需求
      • 基本样式的内部调整
      • 使用漫画风格

    代码说明

    基本用法如下:

    def test_build_csdn_trend_top10_tag_race():
        input = InputMeta(
            type='csv',
            path='data/csdn_trends_top10_month/csdn_index_top_10.csv',
            month_field='date',
            name_field='tag_name',
            count_field='index_value',
            audio='pub/mali.mp3'
        )
    
        output = OutputMeta(
            path='pub/csdn_trends_top10_month',
            ext='gif',
            title='CSDN topN指数月排行榜',
            x_label='csdn.net/trends',
            y_label='指数',
            month_count=None
        )
    
        top = Top(input, output)
        top.build()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    Top 类的构造函数传入两个参数:input: InputMetaoutput: OutputMeta。很多Python代码的参数能有几十个参数,通过InputMetaOutputMeta 两个dataclass可以让使用更友好:

    @dataclass
    class InputMeta:
        '''
        type: 指定类型,如果是 "json_str" 表示一个JSON文件夹,如果是"csv"表示一个csv文件
        JSON 文件夹:
            约定每个文件的文件名是月份,每个JSON文件是一个数组,数组元素是标签统计信息
            name_field: 指定标签名字的字段名
            count_field: 指定标签月份统计信息的字段名
        CSV 文件
            month_field: 指定月份字段
            name_field: 指定标签名字的字段名
            count_field: 指定标签月份统计信息的字段名
            audio: 音频
        '''
        type: str
        path: str
        month_field: str
        name_field: str
        count_field: str
        audio: str
    
    
    @dataclass
    class OutputMeta:
        '''
        输出配置
        path: 输出路径
        title: 标题
        x_label: X轴名字
        y_label: Y轴名字
        month_count: 绘制月份,用来调试,使用较少的月份快速查看输出效果
        '''
        path: str
        ext: str
        title: str
        x_label: str
        y_label: str
        month_count: int
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    Top 类的 build 里的处理流程包括:

    • 转换输入数据到每月一行的 DataFrame
    • 每12个月数据生成一个竞赛动图 GIF
      • 原因之一:太大的GIF文件生成会有内存占用问题,分片处理。
      • 原因之二:分片后,规避出错时要从头再来的问题。
    • 合并多个GIF,生成一个MP4文件
    • 如果输入指定了音频文件,使用音频源采用repeat方式与MP4合成轨道并输出带音效的文件

    输出目录pub下的文件不提交到git仓库,需要注意的是,构建过程中不同平台上的中文字体会有差异,目前适配了Mac和Linux的字体,其他平台待测试。

    实例:CSDN topN 指数月排行榜竞赛动图

    CSDN指数

    CSDN 指数是基于自 2000 年以来 CSDN 平台产生的海量内容数据、用户行为计算而来,作为中国最大专业 IT 技术社区,CSDN 指数具备高度权威性,您可通过查询关键字,用以进行技术领域趋势分析、技术选型变迁历史探索、技术内容消费特征洞察、开发者岗位需求预测等。

    我们用 CSDN 指数的数据做了一个topN 指数月排行榜竞赛动图

    竞赛动画部分片段GIF:
    在这里插入图片描述

    完整版本请看

    数据可视化

    数据经过可视化处理后,可以发现数据间的规律,欢迎对项目提交贡献。开箱即用的漫画风格topN竞赛动图:https://gitcode.net/csdn/topn_race

    这个项目在6月份的时候做过一个版本,对问答的历年标签月排行榜做了一次渲染。当时一次性跑数据渲染比较久,这次再做的时候想到了一个原因应该是同一个GIF整体渲染可能会导致性能越来越慢。于是第一个改进的思路就是分片渲染,再做合成。

    分片操作的过程中,也会顺便产生满足多种需要的输出考虑,例如最后一片的最后一帧会增加停留时长,避免动图到最后一帧一闪而过;例如最后一片也会生成一个小于5M的摘要GIF,用来写博客的时候上传片段GIF使用:

    class Top:
        ...
        def build(self):
            ...
            max_rows = self.df.shape[0]
            i = 0
            j = 0
            df = self.df
            gifs = []
            os.makedirs(self.output, exist_ok=True)
            while i < max_rows:
                end = i+12
                if end >= max_rows:
                    end = max_rows+1
                step = end-i
    
                filename = os.path.join(self.output, f'{j}.{self.ext}')
                if i+step >= max_rows:
                    # 最后一个
                    last_df = df[i:end]
    
                    # 生成一个短摘要
                    min_half = 5
                    if min_half > last_df.shape[0]:
                        min_half = 0
                    self.df = last_df[min_half:]
                    filename_abstracts = os.path.join(
                        self.output, f'{j}_abstracts.{self.ext}')
                    self.__build_race(filename_abstracts)
    
                    # 加强最后一帧
                    self.df = last_df
                    for k in range(0, 12):
                        self.df = self.df.append(df[end-2:end])
                    self.__build_race(filename)
                else:
                    self.df = df[i:end]
                    self.__build_race(filename)
    
                gifs.append(filename)
                i += step
                j += 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    其次很多这样的库包含一堆的参数,例如 topn_race 下层使用的原始库bar_chart_race的代码就是这样的。实际上这里有一个经典的设计模式是可以解决此类代码的组织问题:Builder模式。我觉的后续改进是可以改造下它的代码。不要用一堆的构造函数参数让使用者很难用,通过Builder模式是可以轻易对同一个库的不同使用情景做模块化接口设计。这块后面可以用来进一步改造bar_chart_race的代码。Python 代码越是灵活,越是要在写的过程中注意简洁的基础上有好的设计。

    一个多道程序的内部会有很多重要的实际干活的重型关节代码,如果没有一些控制逻辑,多次运行不能保持轻量,会让人害怕。举个例子,渲染的多个关键环节,都应该加入一些规避不必要的重复操作的判定逻辑:

    例如,判定文件已存在,是否需要覆盖,这样你就可以放心的多次操作

    class Top:
        ...
        def build(self):
            ...
            # 合并 gif 生成mp4
            all = f'{self.output}.mp4'
            if os.path.exists(all):
                ret = input(f"文件:{all}已存在,是否覆盖?[y/n]:")
                if ret == 'y':
                    concat_gif_list(gifs, all)
            else:
                concat_gif_list(gifs, all)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    潜在需求

    • 完备的全平台字体支持
    • 支持为条形图增加关联的「弹幕文本」
    • 增加片头和片尾渲染(保持很短),让它接近代码微电影
    • 进一步解决性能问题
    • 使用Flask支持服务化,支持在线部署和调用

    –end–

  • 相关阅读:
    为什么我从 Swift 转向 Flutter,你也应该这样做
    如何让背景色在任何设备宽高都能填充整个屏幕
    Windows线程 信号量 CreateSemaphore创建信号量、RelaseSemaphore设置信号量
    Linux安装Redis操作
    Spring 01 (之oic)
    realvnc-server-aarch64-ubuntu
    SSM学习——数据源对象管理与加载properties文件(4)
    国内厨电行业数据分析
    毕业设计 基于51单片机的车辆防追尾系统 - 物联网 嵌入式
    数据同步到Redis消息队列,并实现消息发布/订阅
  • 原文地址:https://blog.csdn.net/huanhuilong/article/details/122232176