将pandas数据框的"Out[]"表保存为图像

17

这个功能可能看起来没什么用,但对我会非常有帮助。我想保存在Canopy IDE中获得的输出。我认为这不是特定于Canopy的,但为了清晰起见,我使用的就是Canopy。例如,我的控制台Out [2] 就是我想要的:

enter image description here

我认为它的格式很好,每次都重新生成这样的输出而不是将其保存会浪费时间。所以我的问题是,如何控制这个图像?理想情况下,实现方式应该类似于标准方法,可以像这样完成:

from matplotlib.backends.backend_pdf import PdfPages

pp = PdfPages('Output.pdf')
fig = plt.figure() 
ax = fig.add_subplot(1, 1, 1)
df.plot(how='table')
pp.savefig()
pp.close()

注意:我意识到之前已经有一个非常相似的问题被问过了(如何将Pandas dataframe/series数据保存为图形?),但它从未得到答案,而我认为我已经更清楚地表达了这个问题。


如果您想重新开始,请访问https://dev59.com/Bmoy5IYBdhLWcg3wfOB4 - Keith
2
DataFrame.to_html() 的输出有什么问题呢?使用类似 Beautiful Soup 的标准 HTML 分析工具,你可以轻松地抓取单元格内容。你想要一个展示如何实现的答案吗?你说你想要访问单元格内容,但你也说你想要一个 PDF。这两个要求似乎有冲突。 - holdenweb
我有点困惑你对赏金的要求,不想提供一个不能让你更接近目标的答案。显然你已经知道了DataFrameto_html(以及你提供的链接中的to_latex)选项。那个选项不能满足你的需求吗?你可以将LaTeX嵌入到Matplotlib图中。你想知道如何将HTML嵌入到PDF中吗? - J Richard Snape
@Keith 我猜想你想要做什么,于是我还是添加了一个答案-如果符合你的要求,请告诉我。这种方法采用了不同的方式来解决问题,没有使用matplotlib的pdf后端作为pdf渲染解决方案。 - J Richard Snape
3个回答

6

这里有一个有点hackish的解决方案,但它可以完成任务。你想要一个.pdf文件,但你得到了一个额外的.png文件。 :)

import numpy as np
import pandas as pd
from matplotlib.backends.backend_pdf import PdfPages
import matplotlib.pyplot as plt

from PySide.QtGui import QImage
from PySide.QtGui import QPainter
from PySide.QtCore import QSize
from PySide.QtWebKit import QWebPage

arrays = [np.hstack([ ['one']*3, ['two']*3]), ['Dog', 'Bird', 'Cat']*2]
columns = pd.MultiIndex.from_arrays(arrays, names=['foo', 'bar'])
df =pd.DataFrame(np.zeros((3,6)),columns=columns,index=pd.date_range('20000103',periods=3))

h = "<!DOCTYPE html> <html> <body> <p> " + df.to_html() + " </p> </body> </html>";
page = QWebPage()
page.setViewportSize(QSize(5000,5000))

frame = page.mainFrame()
frame.setHtml(h, "text/html")

img = QImage(1000,700, QImage.Format(5))
painter = QPainter(img)
frame.render(painter)
painter.end()
a = img.save("html.png")

pp = PdfPages('html.pdf')
fig = plt.figure(figsize=(8,6),dpi=1080) 
ax = fig.add_subplot(1, 1, 1)
img2 = plt.imread("html.png")
plt.axis('off')
ax.imshow(img2)
pp.savefig()
pp.close()

欢迎编辑。


4
我认为你的IDE正在呈现一个HTML表格。这就是ipython笔记本所做的事情。
你可以通过以下方式获取它的句柄:
from IPython.display import HTML
import pandas as pd
data = pd.DataFrame({'spam':['ham','green','five',0,'kitties'],
                     'eggs':[0,1,2,3,4]})
h = HTML(data.to_html())
h

并保存至HTML文件中:

my_file = open('some_file.html', 'w')
my_file.write(h.data)
my_file.close()

好的,这让我完成了一半,但我没有任何HTML经验。我想将HTML对象放入我保存绘图的pdf文件中。在我的原始问题中有一个玩具示例。 - Keith
正如你所说“那么我的问题是,我怎样才能掌握这个数字?”,这就是我的回答。表格一定要保存为.pdf格式吗?我已经更新了答案,将html对象保存到文件中。 - Laurence Billingham
@user262536 我不知道如何立即将HTML转换为.pdf。这个SO问题可能会有所帮助:(http://stackoverflow.com/questions/4659058/how-to-save-html-elements-to-jpeg-png-or-pdf-using-python)。另一种方法可能是使用`pandas.DataFrame.to_latex()`方法,并与pdflatex或类似工具一起编译,连同图形一起。不过我也从未尝试过这样做。 - Laurence Billingham
抱歉,我的意思是如何在与matplotlib类相关的情况下获取句柄。就像我如何让那个表格输出类似于从matplotlib.pyplot.imread或matplotlib.pyplot.plot返回的内容一样。我应该更清楚地表达。 - Keith

2
我认为这里需要的是在将图表输出到PDF文件时,与表格输出方式保持一致的方法。
我的第一个想法不是使用matplotlib后端,即...
from matplotlib.backends.backend_pdf import PdfPages

因为它在格式选项上有些受限,并倾向于将表格格式化为图像(从而使表格文本无法选择),所以我认为这有些局限性。
如果你想在PDF中混合使用数据框输出和Matplotlib绘图,但不使用Matplotlib PDF后端,我可以想到两种方法:
  1. 像以前一样生成Matplotlib图的PDF,然后在之后插入包含数据框表格的页面。我认为这是一种困难的选择。
  2. 使用其他库来生成PDF。我下面演示了一种选项。

首先,安装 xhtml2pdf 库。这个库似乎支持有些不完整,但是 在Github上活跃 并且有一些 基本的使用文档在这里。你可以通过 pip 安装它,例如: pip install xhtml2pdf 一旦你完成了安装,这里有一个最简单的示例,其中包含一个 matplotlib 图形,然后是表格(所有文本可选择),然后是另一个图形。你可以尝试调整 CSS 等来更改格式以满足您的确切要求,但我认为这已经符合要求了。
from xhtml2pdf import pisa             # this is the module that will do the work
import numpy as np
import pandas as pd
from matplotlib.backends.backend_pdf import PdfPages
import matplotlib.pyplot as plt

# Utility function
def convertHtmlToPdf(sourceHtml, outputFilename):
    # open output file for writing (truncated binary)
    resultFile = open(outputFilename, "w+b")

    # convert HTML to PDF
    pisaStatus = pisa.CreatePDF(
            sourceHtml,                # the HTML to convert
            dest=resultFile,           # file handle to recieve result
            path='.')                  # this path is needed so relative paths for 
                                       # temporary image sources work

    # close output file
    resultFile.close()                 # close output file

    # return True on success and False on errors
    return pisaStatus.err

# Main program
if __name__=='__main__':   
 
    arrays = [np.hstack([ ['one']*3, ['two']*3]), ['Dog', 'Bird', 'Cat']*2]
    columns = pd.MultiIndex.from_arrays(arrays, names=['foo', 'bar'])
    df = pd.DataFrame(np.zeros((3,6)),columns=columns,index=pd.date_range('20000103',periods=3))

    # Define your data
    sourceHtml = '<html><head>'         
    # add some table CSS in head
    sourceHtml += '''<style>
                     table, td, th {
                           border-style: double;
                           border-width: 3px;
                     }

                     td,th {
                           padding: 5px;
                     }
                     </style>'''
    sourceHtml += '</head><body>'
    #Add a matplotlib figure(s)
    plt.plot(range(20))
    plt.savefig('tmp1.jpg')
    sourceHtml += '\n<p><img src="tmp1.jpg"></p>'
    
    # Add the dataframe
    sourceHtml += '\n<p>' + df.to_html() + '</p>'
    
    #Add another matplotlib figure(s)
    plt.plot(range(70,100))
    plt.savefig('tmp2.jpg')
    sourceHtml += '\n<p><img src="tmp2.jpg"></p>'
    
    sourceHtml += '</body></html>'
    outputFilename = 'test.pdf'
    
    convertHtmlToPdf(sourceHtml, outputFilename)

注意:目前在xhtml2pdf中似乎存在一个错误,导致某些CSS无法被遵守。特别与此问题相关的是,似乎不可能在表格周围获得双重边框。

编辑

针对评论,很明显有一些用户(至少是 @Keith 回答并授予赏金的用户!)希望表格可选择,但肯定在 matplotlib 轴上。这与原始方法有些更符合。因此,这里提供了一种仅使用 matplotlib 和 matplotlib 对象的 pdf 后端的方法。我不认为表格看起来很好 - 特别是层次列标题的显示,但我想这是一个选择问题。感谢 这个答案 和评论提供的关于表格显示格式的方式。

import numpy as np
import pandas as pd
from matplotlib.backends.backend_pdf import PdfPages
import matplotlib.pyplot as plt

# Main program
if __name__=='__main__':   
    pp = PdfPages('Output.pdf')
    arrays = [np.hstack([ ['one']*3, ['two']*3]), ['Dog', 'Bird', 'Cat']*2]
    columns = pd.MultiIndex.from_arrays(arrays, names=['foo', 'bar'])
    df =pd.DataFrame(np.zeros((3,6)),columns=columns,index=pd.date_range('20000103',periods=3))

    plt.plot(range(20))
    pp.savefig()
    plt.close()

    # Calculate some sizes for formatting - constants are arbitrary - play around
    nrows, ncols = len(df)+1, len(df.columns) + 10
    hcell, wcell = 0.3, 1.
    hpad, wpad = 0, 0   
    
    #put the table on a correctly sized figure    
    fig=plt.figure(figsize=(ncols*wcell+wpad, nrows*hcell+hpad))
    plt.gca().axis('off')
    matplotlib_tab = pd.tools.plotting.table(plt.gca(),df, loc='center')    
    pp.savefig()
    plt.close()

    #Add another matplotlib figure(s)
    plt.plot(range(70,100))
    pp.savefig()
    plt.close()
  
    pp.close()

谢谢,这让我进展得更远了。上面的图像选项可以获得格式,但没有选择,而这个可以选择,但没有格式。我会给你赏金,但我要尝试看看能否获得更合理的格式。 - Keith
好的,谢谢。现在我知道你想要什么了,我会看一下如何添加一些CSS来为表格设置样式,文档中似乎表明这是可能的。 - J Richard Snape
加入一些CSS - 不幸的是,它似乎忽略了border-style: double指令,但是border-width和padding似乎被尊重,并使布局变得更好。如果您真的需要特定的布局,我相信可以使用更多的CSS来实现。 - J Richard Snape
我认为我不需要特定的布局,我只是认为上面从ipython中显示出来的那个看起来不错。我相信许多其他布局同样好,但最初我认为有某种方法可以直接获取它。无论如何,你上面的代码对我来说无法运行。我得到了>> CSSParseError: Selector name or qualifier expected:: (u'', u'</head><body>\n<p><ta')。 - Keith
是的,那样会好很多。我认为通过调整样式设置,我可以做到这一点。我仍然有兴趣尝试找到一种方法将表格放入 matplotlib 轴中。这将是 Pandas 的一个不错的补丁。 - Keith
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接