使用 Pandas, Jinja 和 WeasyPrint,轻松创建一个 PDF 报表
我們都知道,Pandas 擅長處理大量數據并以多種文本和視覺表示形式對其進行總結,它支持將結構輸出到 CSV、Excel、HTML、json 等。但是如果我們想將多條數據合并到一個文檔中,就有些復雜了。例如,如果要將兩個 DataFrames 放在一張 Excel 工作表上,則需要使用 Excel 庫手動構建輸出。雖然可行,但并不簡單。本文將介紹一種將多條信息組合成 HTML 模板,然后使用 Jinja 模板和 WeasyPrint 將其轉換為獨立 PDF 文檔的方法,一起來看看吧~
總體流程
如報告文章所示,使用 Pandas 將數據輸出到 Excel 文件中的多個工作表或從 pandas DataFrames 創建多個 Excel 文件都非常方便。但是,如果我們想將多條信息組合到一個文件中,那么直接從 Pandas 中完成的簡單方法卻并不多,下面我們來探索一條可行的簡單方法
在本文中,我將使用以下流程來創建多頁 PDF 文檔
這種方法的好處是我們可以將自己的工具替換到此工作流程中。不喜歡用 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:
將數據進行透視表匯總處理
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:
模板
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 報告:
雖然報告生成了,但是看起來很難看啊,我們來優化下,添加 CSS
這里使用 blue print 的 typography.css 作為我們的 style.css 的基礎,它有以下幾個優點:
它比較小且易于理解
它可以在 PDF 引擎中工作而不會引發錯誤和警告
它包括看起來相當不錯的基本表格格式
可以看到,僅僅添加一行代碼,產生的效果卻大大不同
更復雜的模板
為了生成更有用的報告,我們將結合上面顯示的匯總統計數據,并將報告拆分為每個經理包含一個單獨的 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 報表就完成了,整體效果如下
完整代碼:
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"])各位伙伴們好,詹帥本帥搭建了一個個人博客和小程序,匯集各種干貨和資源,也方便大家閱讀,感興趣的小伙伴請移步小程序體驗一下哦!(歡迎提建議)推薦閱讀牛逼!Python常用數據類型的基本操作(長文系列第①篇) 牛逼!Python的判斷、循環和各種表達式(長文系列第②篇)牛逼!Python函數和文件操作(長文系列第③篇)牛逼!Python錯誤、異常和模塊(長文系列第④篇)總結
以上是生活随笔為你收集整理的使用 Pandas, Jinja 和 WeasyPrint,轻松创建一个 PDF 报表的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 最完整的时间序列分析和预测(含实例及代码
- 下一篇: 一些著名的软件都用什么语言编写?