Pandas + Jinja,轻松创建一个 PDF 报表

来源:萝卜大杂烩

我们都知道,Pandas 擅长处理大量数据并以多种文本和视觉表示形式对其进行总结,它支持将结构输出到 CSV、Excel、HTML、json 等。但是如果我们想将多条数据合并到一个文档中,就有些复杂了。例如,如果要将两个 DataFrames 放在一张 Excel 工作表上,则需要使用 Excel 库手动构建输出。虽然可行,但并不简单。本文将介绍一种将多条信息组合成 HTML 模板,然后使用 Jinja 模板和 WeasyPrint 将其转换为独立 PDF 文档的方法,一起来看看吧~

总体流程

如报告文章所示,使用 Pandas 将数据输出到 Excel 文件中的多个工作表或从 pandas DataFrames 创建多个 Excel 文件都非常方便。但是,如果我们想将多条信息组合到一个文件中,那么直接从 Pandas 中完成的简单方法却并不多,下面我们来探索一条可行的简单方法

在本文中,我将使用以下流程来创建多页 PDF 文档

873224ef6af59ad053023d5221544f3b.png

这种方法的好处是我们可以将自己的工具替换到此工作流程中。不喜欢用 Jinja?那么可以插入 mako 或其他任何模板工具

工具选择

首先,我们使用 HTML 作为模板语言,因为它可能是生成结构化数据并允许设置相对丰富的格式的最简单方法

其次,选择 Jinja 是因为我有使用 Django/Flask 的经验,上手比较容易

这个工具链中最困难的部分是弄清楚如何将 HTML 呈现为 PDF。我觉得目前还没有非常好的解决方案,我这里选择了 WeasyPrint,大家也可以尝试一下其他的工具

数据处理

导入模块,读取销售信息

from __future__ import print_function
import pandas as pd
import numpy as np
df = pd.read_excel("sales-funnel.xlsx")
df.head()

Output:

91ba2c8c13bcc9b32de85e46eb65fb4f.png

将数据进行透视表汇总处理

sales_report = pd.pivot_table(df, index=["Manager", "Rep", "Product"], values=["Price", "Quantity"],
                           aggfunc=[np.sum, np.mean], fill_value=0)
sales_report.head()

Output:

f79391f9b9ea55de2fc43b41c94a7fd3.png

模板

Jinja 模板非常强大,支持许多高级功能,例如沙盒执行和自动转义等等

Jinja 的另一个不错的功能是它包含多个内置过滤器,这将允许我们以在 Pandas 中难以做到的方式格式化我们的一些数据

为了在我们的应用程序中使用 Jinja,我们需要做 3 件事:

  • 创建模板

  • 将变量添加到模板上下文中

  • 将模板渲染成 HTML

我们先创建一个简单的模板 myreport.html

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>{{ title }}</title>
</head>
<body>
    <h2>Sales Funnel Report - National</h2>
     {{ national_pivot_table }}
</body>
</html>

此代码的两个关键部分是 {{ title }} 和 {{ national_pivot_table }}。它们本质上是我们在渲染文档时将提供的变量的占位符

要填充这些变量,我们需要创建一个 Jinja 环境并获取我们的模板:

from jinja2 import Environment, FileSystemLoader
env = Environment(loader=FileSystemLoader('.'))
template = env.get_template("myreport.html")

在上面的示例中,我们假设模板位于当前目录中

另一个关键组件是 env 的创建,这个变量是我们将内容传递给模板的方式。我们创建一个名为 template_var 的字典,其中包含我们要传递给模板的所有变量

变量的名称与我们的模板匹配

template_vars = {"title" : "Sales Funnel Report - National",
                 "national_pivot_table": sales_report.to_html()}

最后一步是使用输出中包含的变量来呈现 HTML,这将创建一个字符串,我们最终将传递给我们的 PDF 创建引擎

html_out = template.render(template_vars)

生成 PDF

PDF 创建部分也相对简单,我们需要做一些导入并将一个字符串传递给 PDF 生成器

from weasyprint import HTML
HTML(string=html_out).write_pdf("report.pdf")

此命令会创建一个如下所示的 PDF 报告:

904576eb43e50c4fe211eb1b4df76ab0.png

虽然报告生成了,但是看起来很难看啊,我们来优化下,添加 CSS

这里使用 blue print 的 typography.css 作为我们的 style.css 的基础,它有以下几个优点:

  • 它比较小且易于理解

  • 它可以在 PDF 引擎中工作而不会引发错误和警告

  • 它包括看起来相当不错的基本表格格式

HTML(string=html_out).write_pdf(args.outfile.name, stylesheets=["style.css"])

1509d0ca74a175c58b91fb9e0c280ca9.png

可以看到,仅仅添加一行代码,产生的效果却大大不同

更复杂的模板

为了生成更有用的报告,我们将结合上面显示的汇总统计数据,并将报告拆分为每个经理包含一个单独的 PDF 页面

让我们从更新的模板(myreport.html)开始:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>{{ title }} </title>
</head>
<body>
<div class="container">
    <h2>Sales Funnel Report - National</h2>
     {{ national_pivot_table }}
    {% include "summary.html" %}
</div>
<div class="container">
    {% for manager in Manager_Detail %}
        <p style="page-break-before: always" ></p>
        <h2>Sales Funnel Report - {{manager.0}}</h2>
        {{manager.1}}
        {% include "summary.html" %}
    {% endfor %}
</div>
</body>
</html>

我们注意到的第一件事是有一个包含语句,它提到了另一个文件。包含允许我们引入一段 HTML 并在代码的不同部分重复使用它。在这种情况下,摘要包含一些我们希望在每个报告中包含的简单的国家级统计数据,以便管理人员可以将他们的绩效与全国平均水平进行比较。

以下是 summary.html 的样子:

<h3>National Summary: CPUs</h3>
    <ul>
        <li>Average Quantity: {{CPU.0|round(1)}}</li>
        <li>Average Price: {{CPU.1|round(1)}}</li>
    </ul>
<h3>National Summary: Software</h3>
    <ul>
        <li>Average Quantity: {{Software.0|round(1)}}</li>
        <li>Average Price: {{Software.1|round(1)}}</li>
    </ul>

在此代码段中,看到我们可以访问一些其他变量:CPU 和 Software 。其中每一个都是一个 python 列表,其中包括 CPU 和软件销售的平均数量和价格

还注意到我们使用管道|将每个值四舍五入到小数点后 1 位。这是使用 Jinja 过滤器的一个具体示例

还有一个 for 循环允许我们在报告中显示每个经理的详细信息。Jinja 的模板语言只包含一个非常小的代码子集,它会改变控制流

附加统计信息

下面编写供模板调用的函数和代码

一个简单的汇总函数

def get_summary_stats(df,product):
    """
    For certain products we want National Summary level information on the reports
    Return a list of the average quantity and price
    """
    results = []
    results.append(df[df["Product"]==product]["Quantity"].mean())
    results.append(df[df["Product"]==product]["Price"].mean())
    return results

创建经理详细信息

manager_df = []
for manager in sales_report.index.get_level_values(0).unique():
    manager_df.append([manager, sales_report.xs(manager, level=0).to_html()])

最后,使用以下变量调用模板

template_vars = {"title" : "National Sales Funnel Report",
                 "CPU" : get_summary_stats(df, "CPU"),
                 "Software": get_summary_stats(df, "Software"),
                 "national_pivot_table": sales_report.to_html(),
                 "Manager_Detail": manager_df}
# Render our file and create the PDF using our css style file
html_out = template.render(template_vars)
HTML(string=html_out).write_pdf("report.pdf",stylesheets=["style.css"])

这样我们的 pdf 报表就完成了,整体效果如下

4d0357be151fb0ea6ae47fe78c45938a.gif

完整代码:

from __future__ import print_function
import pandas as pd
import numpy as np
import argparse
from jinja2 import Environment, FileSystemLoader
from weasyprint import HTML


def create_pivot(df, infile, index_list=["Manager", "Rep", "Product"], value_list=["Price", "Quantity"]):
    """
    Create a pivot table from a raw DataFrame and return it as a DataFrame
    """
    table = pd.pivot_table(df, index=index_list, values=value_list,
                           aggfunc=[np.sum, np.mean], fill_value=0)
    return table

def get_summary_stats(df,product):
    """
    For certain products we want National Summary level information on the reports
    Return a list of the average quantity and price
    """
    results = []
    results.append(df[df["Product"]==product]["Quantity"].mean())
    results.append(df[df["Product"]==product]["Price"].mean())
    return results

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='Generate PDF report')
    parser.add_argument('infile', type=argparse.FileType('r'),
    help="report source file in Excel")
    parser.add_argument('outfile', type=argparse.FileType('w'),
    help="output file in PDF")
    args = parser.parse_args()

    df = pd.read_excel(args.infile.name)
    sales_report = create_pivot(df, args.infile.name)

    manager_df = []
    for manager in sales_report.index.get_level_values(0).unique():
        manager_df.append([manager, sales_report.xs(manager, level=0).to_html()])

    env = Environment(loader=FileSystemLoader('.'))
    template = env.get_template("myreport.html")
    template_vars = {"title" : "National Sales Funnel Report",
                     "CPU" : get_summary_stats(df, "CPU"),
                     "Software": get_summary_stats(df, "Software"),
                     "national_pivot_table": sales_report.to_html(),
                     "Manager_Detail": manager_df}

    html_out = template.render(template_vars)
    HTML(string=html_out).write_pdf(args.outfile.name,stylesheets=["style.css"])

-------- End --------

1e6c187a4a7e99dc8f20e35d64853a8d.png
精选资料

回复关键词,获取对应的资料:

关键词资料名称
600《Python知识手册》
md《Markdown速查表》
time《Python时间使用指南》
str《Python字符串速查表》
pip《Python:Pip速查表》
style《Pandas表格样式配置指南》
mat《Matplotlib入门100个案例》
px《Plotly Express可视化指南》
精选内容

数据科学: VS Code 中 Python配置使用指南 | 财经工具 Tushare | Matplotlib 最有价值的 50 个图表

书籍阅读: 如何阅读一本书 | 巴菲特之道 | 价值 | 原则 | 投资最重要的事 | 戴维斯王朝 | 客户的游艇在哪里 | 刻意练习 | 林肯传 | 金字塔原理

投资小结: 2021Q4 | 2021Q3 | 2021Q2 | 2021Q1 | 2020Q4

精选视频

可视化: Plotly Express

财经: Plotly在投资领域的应用 | 绘制K线图表

排序算法: 汇总 | 冒泡排序 | 选择排序 | 快速排序 | 归并排序 | 堆排序 | 插入排序 | 希尔排序 | 计数排序 | 桶排序 | 基数排序

0db18cfbd98f093390ec72cf750213a0.png
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值