当前位置: 首页 > 编程日记 > 正文

使用 Pandas、Jinja 和 WeasyPrint,轻松创建一个 PDF 报表

05d430ab8d7f2f265abfeb42af02ccc6.gif

作者 |周萝卜

来源 |萝卜大杂烩

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

总体流程

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

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

6e40f53f0dd35f92009422611cec34e3.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:

5dc69226dfd32058d16ff8df572d9e34.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:

91bf2ef8c0a800fcf572b1983d6c5393.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 报告:

02c1ed85751ccfa38f23afab256eb330.png

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

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

  • 它比较小且易于理解

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

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

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

0ed612ae2e630d7f79425bcbeb71b7e7.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 reportsReturn 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 报表就完成了,整体效果如下

235590dc9afd915b45fc0c5996b2a47a.gif

完整代码:

from __future__ import print_function
import pandas as pd
import numpy as np
import argparse
from jinja2 import Environment, FileSystemLoader
from weasyprint import HTMLdef 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 tabledef get_summary_stats(df,product):"""For certain products we want National Summary level information on the reportsReturn 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 resultsif __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"])

1fab453b280ceda58a0807acaa671b37.gif

技术

100行python代码制作鞭炮

资讯

大型模型语言能够理解吗?

技术

创意十足的Python命令行工具

资讯

游戏圈地震级消息,微软收购动视暴雪

eab1c71634b86bb5bcf5f7f2abec1266.png

分享

7cb7459f52904c0c162ce7d26f5fed3d.png

点收藏

1af83dcf12fda4ae97f7377c41ea01ad.png

点点赞

bbc596bed28f76a782e9b4b4290a232f.png

点在看

相关文章:

通过Excel生成批量SQL语句

项目中有时会遇到这样的要求&#xff1a;用户给发过来一些数据&#xff0c;要我们直接给存放到数据库里面&#xff0c;有的是Insert&#xff0c;有的是Update等等&#xff0c;少量的数据我们可以采取最原始的办法&#xff0c;也就是在SQL里面用Insert into来实现&#xff0c;但…

抵御「黄貂鱼」攻击,谷歌使出禁用2G「大招」

整理 | 于轩 责编 | 张红月出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;你还在使用2G吗&#xff1f;相信很多人第一反应都是“怎么可能&#xff1f;”确实&#xff0c;现在绝大数人都在使用技术成熟的4G网络&#xff0c;以及更高网速的5G。但是你有注意到…

Crontab运行php脚本

首先&#xff0c;确认 PHP 可执行文件的位置 —— 对于大多数 Linux 系统&#xff0c;几乎肯定是 /usr/bin/php。如果不确定其位置&#xff0c;请在命令行中键入 which php 并查看响应内容。 其次&#xff0c;键入以下代码&#xff0c;确保将 /usr/bin/php 替换为 PHP 可执行文…

iOS原生如何加载HTML中img标签的图片

原文出自&#xff1a;iOS原生如何加载HTML中img标签的图片 前言 最近iOS App项目中使用Webview加载H5页面比较多&#xff0c;也有不少朋友经常问到这个问题&#xff0c;在这里我也学习学习如何通过iOS原生的方式来加载H5页面中的图片然后让webview显示图片。 相信有很多朋友也…

Python3 的urllib实例

在Python3中合并了 urllib 和 urllib2&#xff0c; 统一命名为 urllib 了&#xff0c;我觉得这样更加合理了。让我们可以像读取本地文件一样读取WEB上的数据。封装了一个类&#xff0c;供以后方便使用吧&#xff01;并附带有许多的应用实例。 一、封装的类 #!/usr/bin/env pyth…

Java中Filter、Servlet、Listener的学习

1、Filter的功能filter功能&#xff0c;它使用户可以改变一个 request和修改一个response. Filter 不是一个servlet,它不能产生一个response,它能够在一个request到达servlet之前预处理request,也可以在离开 servlet时处理response.换种说法,filter其实是一个”servlet chainin…

CentOS 6安装DHCP

#wget ftp://ftp.isc.org/isc/dhcp/dhcp-4.2.3/dhcp-4.2.3.tar.gz #tar xvzf dhcp-4.2.3.tar.gz# cd dhcp-4.2.3#./configure #make #make install

小米AI实验室六篇论文获ICASSP2022收录,多模态语音唤醒挑战赛夺冠

1月22日&#xff0c;全球语音、声学顶级会议ICASSP 2022公布了论文入选名单&#xff0c;小米AI实验室6篇学术论文被接收。小米“自由说”系统在MISP&#xff08;基于多模态信息的语音处理&#xff09;挑战赛中荣获多模态语音唤醒第一名和多模态语音识别第二名&#xff0c;并受邀…

React + Koa 实现服务端渲染(SSR)

⚛️React是目前前端社区最流行的UI库之一&#xff0c;它的基于组件化的开发方式极大地提升了前端开发体验&#xff0c;React通过拆分一个大的应用至一个个小的组件&#xff0c;来使得我们的代码更加的可被重用&#xff0c;以及获得更好的可维护性&#xff0c;等等还有其他很多…

11 款可替代 top 命令的工具!

‍‍作者 | JackTian来源 | 杰哥的IT之旅在 Linux 环境下 top 命令都不陌生&#xff0c;它以实时动态的方式查看系统的整体运行情况&#xff0c;综合了多方信息监测系统性能和运行信息的实用工具&#xff0c;通过 top 命令所提供的互动式界面&#xff0c;可以用热键来进行管理。…

几个重要的RFC

RFC目录 权威无须解释 http://www.ietf.org/rfc/RFC中文目录http://man.chinaunix.net/develop/rfc/default.htm几个常用的RFC参考&#xff1a; RFC1945 超文本传输协议--HTTP/1.0 RFC2616超文本传输协议--HTTP/1.1 对 RFC2068的补充RFC3920可扩展的消息和出席信息协议 (XMPP)…

iOS开发笔记-两种单例模式的写法

iOS开发笔记&#xff0d;两种单例模式的写法 单例模式是开发中最常用的写法之一&#xff0c;iOS的单例模式有两种官方写法&#xff0c;如下&#xff1a; 不使用GCD #import "ServiceManager.h"static ServiceManager *defaultManager;implementation ServiceManager(…

流式大数据处理的三种框架:Storm,Spark和Samza

2019独角兽企业重金招聘Python工程师标准>>> 许多分布式计算系统都可以实时或接近实时地处理大数据流。本文将对三种Apache框架分别进行简单介绍&#xff0c;然后尝试快速、高度概述其异同。 Apache Storm 在Storm中&#xff0c;先要设计一个用于实时计算的图状结构…

CentOS用yum安装X Window

安装X图形界面系统yum list 列出所有可安装的软件包 可以通过 yum grouplist 来查看可能批量安装哪些列表 先装X windows #yum groupinstall X Window System -y 安装GNOME桌面环境#yum groupinstall GNOME Desktop Environment -y 安装KDE桌面环境#yum groupinstall KDE (K D…

Oracle VDI 安装

为什么80%的码农都做不了架构师&#xff1f;>>> 你可以在这里找到本文的原文。 虽然说Oracle已经停止了VDI的开发&#xff0c;之后支持服务业很快停止了。但是&#xff0c;作为经典的桌面虚拟化产品&#xff0c;还是值得研究一番。虽然Oracle VDI的文档已经写的很详…

Python 写了一个网页版的「P图软件」,惊呆了!

作者 | 小欣来源 | Python爱好者集中营今天是开工第一天&#xff0c;这篇文章可以算作是虎年的第一篇干货技术类文章了&#xff0c;今天小编用Python做了一个网页版的“P图软件”&#xff0c;大致的流程在于我们可以将上传的照片进行黑白处理、铅笔素描处理、模糊化处理等一系列…

Template mode HTML5 has not been configured

#thymeleafspring.thymeleaf.prefixclasspath:/templates/spring.thymeleaf.suffix.htmlspring.thymeleaf.cachefalsespring.thymeleaf.content-typetext/htmlspring.thymeleaf.enabledtruespring.thymeleaf.encodingUTF-8spring.thymeleaf.modeHTML5 解决办法&#xff1a;注释…

Java数据结构与算法(第四章栈和队列)

2019独角兽企业重金招聘Python工程师标准>>> 本章涉及的三种数据存储类型&#xff1a;栈、队列和优先级队列。 不同类型的结构 程序员的工具 数组是已经介绍过的数据存储结构&#xff0c;和其他结构&#xff08;链表、树等等&#xff09;一样&#xff0c;都适用于数…

可构建AI的「AI」诞生:几分之一秒内,就能预测新网络的参数

‍‍来源 | 学术头条人工智能在很大程度上是一场数字游戏。当深度神经网络在 10 年前开始超越传统算法&#xff0c;是因为我们终于有了足够的数据和处理能力来充分利用它们。今天的神经网络更依赖于数据和算力。训练网络时&#xff0c;需要仔细调整表征网络的数百万甚至数十亿参…

It is not safe to rely on the system's timezone settings

在写php程序中有时会出现这样的警告&#xff1a; PHP Warning: date(): It is not safe to rely on the systems timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those metho…

.NET MVC+ EF+LINQ 多表联查VIEW显示列表

1.VIEW 页面显示代码 <link href"~/Content/bootstrap.css" rel"stylesheet" /><div class"well"><table class"table"><tr><th>用户名</th><th>地址</th><th>订单编号</th…

从奥运订票系统说起——谈FastCGI 与IT 架构

2008年&#xff0c;对于首都人民来说&#xff0c;没有什么比奥运会更大的事情了。如何买到一张称心如意的比赛门票&#xff0c;也成了很多人的一个梦想。然而&#xff0c;在奥运官网抢票购买的时候&#xff0c;这个梦想却轻易地被网上购票系统的当机击成碎片&#xff0c;很多充…

【哲学百科】文艺复兴及唯理主义时期(公元1500~公元1750)

我为达目的&#xff0c;不择手段-尼古拉.马基雅维利要令习惯于君主统治的民众保有自由是一件多么困难的事情。马基雅维利的观点之一是君主不应受到道德标准的束缚&#xff0c;而应竭尽所能保全自身的荣耀以及所统治的城邦的胜利与繁荣&#xff0c;这种做法随后被人们归为现实主…

如何用 OpenGL 绘制雪花?

作者 | 许向武 责编 | 张红月出品 | CSDN博客看冬奥才知道&#xff0c;阿勒泰不但是中国的“雪都”&#xff0c;还是“人类滑雪起源地”。这个说法是否成立&#xff0c;姑且不论&#xff0c;阿勒泰的雪的确很漂亮。冬奥会有一个宣传片&#xff0c;就是借用一朵阿勒泰雪花…

面试之Hashtable和ConcurrentHashMap

那么要如何保证HashMap的线程安全呢&#xff1f; 方法有很多&#xff0c;比如使用Hashtable或者Collections.synchronizedMap&#xff0c;但是这两位选手都有一个共同的问题&#xff1a;性能。因为不管是读还是写操作&#xff0c;他们都会给整个集合上锁&#xff0c;导致同一时…

PHP动态编译出现Cannot find autoconf

在安装完PHP后,想动态编译PHP的memcache扩展库 cd memcache-2.2.5//usr/local/webserver/php/bin/phpize./configure --with-php-config/usr/local/webserver/php/bin/php-config 但是执行/usr/local/webserver/php/bin/phpize时出现错误:Configuring for:PHP Api Version: …

AnimeGANv2 实现动漫风格迁移,简单操作

作者 | Yunlord出品 | CSDN博客前言之前一直在研究如何将图像动漫化&#xff0c;尝试了阿里云api和百度api&#xff0c;效果都不尽如人意。结果发现了一个宝藏github项目——AnimeGANv2&#xff0c;能够将现实世界场景照片进行动漫风格化。可以看出AnimeGAN的效果非常好&#x…

C#调用win32 api程序实例

1、声明static extern 方法&#xff0c;使用DllImport特性 class MyClass{[DllImport("kernel32", SetLastError true)]public static extern int GetCurrentDirectory(int a, StringBuilder b);} 2、调用 StringBuilder sbnew StringBuilder {Length 250}; MyClas…

Python 之 pip拒绝访问

起因 在我使用pip安装第三方库的时候&#xff0c;控制台提示我升级pip版本 You are using pip version 9.0.1, however version 10.0.1 is available. You should consider upgrading via the python -m pip install --upgrade pip command. 很显然&#xff0c;需要使用这样的指…

Unix / 类 Unix shell 中有哪些很酷很冷门很少用很有用的命令?(转)

著作权归作者所有。 商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处。 作者&#xff1a;孙立伟 链接&#xff1a;http://www.zhihu.com/question/20140085/answer/14107336 来源&#xff1a;知乎 这个问题quora上有人提过 What are some lesser known but useful…