Cómo guardar una tabla Pandas DataFrame como png

Resuelto Shatnerz asked hace 8 años • 13 respuestas

Construí un marco de datos de resultados de pandas. Este marco de datos actúa como una tabla. Hay columnas MultiIndexed y cada fila representa un nombre, es decir, index=['name1','name2',...]al crear el DataFrame. Me gustaría mostrar esta tabla y guardarla como png (o cualquier formato gráfico en realidad). Por el momento, lo más cerca que puedo llegar es convertirlo a html, pero me gustaría un png. Parece que se han hecho preguntas similares, como ¿ Cómo guardar los datos de la serie/marco de datos de Pandas como una figura?

Sin embargo, la solución marcada convierte el marco de datos en un diagrama de líneas (no una tabla) y la otra solución se basa en PySide, que me gustaría mantener alejado simplemente porque no puedo instalarlo en Linux. Me gustaría que este código fuera fácilmente portátil. Realmente esperaba que la creación de tablas en png fuera fácil con Python. Se agradece toda ayuda.

Shatnerz avatar Feb 26 '16 00:02 Shatnerz
Aceptado

Pandas le permite trazar tablas usando matplotlib (detalles aquí ). Por lo general, esto traza la tabla directamente en un gráfico (con ejes y todo) que no es lo que desea. Sin embargo, estos se pueden eliminar primero:

import matplotlib.pyplot as plt
import pandas as pd
from pandas.table.plotting import table # EDIT: see deprecation warnings below

ax = plt.subplot(111, frame_on=False) # no visible frame
ax.xaxis.set_visible(False)  # hide the x axis
ax.yaxis.set_visible(False)  # hide the y axis

table(ax, df)  # where df is your data frame

plt.savefig('mytable.png')

Es posible que el resultado no sea el más bonito, pero puede encontrar argumentos adicionales para la función table() aquí . También gracias a esta publicación por información sobre cómo eliminar ejes en matplotlib.


EDITAR:

Aquí hay una forma (ciertamente bastante complicada) de simular índices múltiples al trazar utilizando el método anterior. Si tiene un marco de datos de índices múltiples llamado df que se ve así:

first  second
bar    one       1.991802
       two       0.403415
baz    one      -1.024986
       two      -0.522366
foo    one       0.350297
       two      -0.444106
qux    one      -0.472536
       two       0.999393
dtype: float64

Primero restablezca los índices para que se conviertan en columnas normales.

df = df.reset_index() 
df
    first second       0
0   bar    one  1.991802
1   bar    two  0.403415
2   baz    one -1.024986
3   baz    two -0.522366
4   foo    one  0.350297
5   foo    two -0.444106
6   qux    one -0.472536
7   qux    two  0.999393

Elimine todos los duplicados de las columnas de índices múltiples de orden superior configurándolas en una cadena vacía (en mi ejemplo solo tengo índices duplicados en "primero"):

df.ix[df.duplicated('first') , 'first'] = '' # see deprecation warnings below
df
  first second         0
0   bar    one  1.991802
1          two  0.403415
2   baz    one -1.024986
3          two -0.522366
4   foo    one  0.350297
5          two -0.444106
6   qux    one -0.472536
7          two  0.999393

Cambie los nombres de las columnas sobre sus "índices" a la cadena vacía

new_cols = df.columns.values
new_cols[:2] = '',''  # since my index columns are the two left-most on the table
df.columns = new_cols 

Ahora llame a la función de tabla pero establezca todas las etiquetas de fila en la tabla en la cadena vacía (esto asegura que no se muestren los índices reales de su gráfico):

table(ax, df, rowLabels=['']*df.shape[0], loc='center')

y listo:

ingrese la descripción de la imagen aquí

Tu tabla multiindexada no tan bonita pero totalmente funcional.

EDITAR: ADVERTENCIAS DE DEPRECACIÓN

Como se señala en los comentarios, la declaración de importación para table:

from pandas.tools.plotting import table

ahora está en desuso en las versiones más recientes de pandas en favor de:

from pandas.plotting import table 

EDITAR: ADVERTENCIAS DE DEPRECACIÓN 2

El ixindexador ahora ha quedado completamente obsoleto , por lo que deberíamos usarlo locen su lugar. Reemplazar:

df.ix[df.duplicated('first') , 'first'] = ''

con

df.loc[df.duplicated('first') , 'first'] = ''
bunji avatar Mar 01 '2016 05:03 bunji

En realidad, existe una biblioteca de Python llamada dataframe_image. Solo haz un

pip install dataframe_image

hacer las importaciones

import pandas as pd
import numpy as np
import dataframe_image as dfi
df = pd.DataFrame(np.random.randn(6, 6), columns=list('ABCDEF'))

y diseña tu mesa si lo deseas mediante:

df_styled = df.style.background_gradient() #adding a gradient based on values in cell

y finalmente:

dfi.export(df_styled,"mytable.png")
Akshay Palakkode avatar Aug 13 '2020 02:08 Akshay Palakkode

La mejor solución a su problema probablemente sea exportar primero su marco de datos a HTML y luego convertirlo utilizando una herramienta de HTML a imagen. La apariencia final se podría modificar mediante CSS.

Las opciones populares para la representación de HTML a imagen incluyen:

  • WeasyPrint
  • wkhtmltopdf/wkhtmltoimage

Supongamos que tenemos un marco de datos llamado df. Podemos generar uno con el siguiente código:

import string
import numpy as np
import pandas as pd


np.random.seed(0)  # just to get reproducible results from `np.random`
rows, cols = 5, 10
labels = list(string.ascii_uppercase[:cols])
df = pd.DataFrame(np.random.randint(0, 100, size=(5, 10)), columns=labels)
print(df)
#     A   B   C   D   E   F   G   H   I   J
# 0  44  47  64  67  67   9  83  21  36  87
# 1  70  88  88  12  58  65  39  87  46  88
# 2  81  37  25  77  72   9  20  80  69  79
# 3  47  64  82  99  88  49  29  19  19  14
# 4  39  32  65   9  57  32  31  74  23  35

Usando WeasyPrint

Este enfoque utiliza un pippaquete instalable, que le permitirá hacer todo utilizando el ecosistema Python. Una desventaja weasyprintes que no parece proporcionar una forma de adaptar el tamaño de la imagen a su contenido . De todos modos, eliminar algo de fondo de una imagen es relativamente fácil en Python/PIL y se implementa en la trim()función siguiente (adaptada desde aquí ). También sería necesario asegurarse de que la imagen sea lo suficientemente grande, y esto se puede hacer con @page sizela propiedad de CSS.

El código es el siguiente:

import weasyprint as wsp
import PIL as pil


def trim(source_filepath, target_filepath=None, background=None):
    if not target_filepath:
        target_filepath = source_filepath
    img = pil.Image.open(source_filepath)
    if background is None:
        background = img.getpixel((0, 0))
    border = pil.Image.new(img.mode, img.size, background)
    diff = pil.ImageChops.difference(img, border)
    bbox = diff.getbbox()
    img = img.crop(bbox) if bbox else img
    img.save(target_filepath)


img_filepath = 'table1.png'
css = wsp.CSS(string='''
@page { size: 2048px 2048px; padding: 0px; margin: 0px; }
table, td, tr, th { border: 1px solid black; }
td, th { padding: 4px 8px; }
''')
html = wsp.HTML(string=df.to_html())
html.write_png(img_filepath, stylesheets=[css])
trim(img_filepath)

tabla_weasyprint


Usando wkhtmltopdf/wkhtmltoimage

Este enfoque utiliza una herramienta externa de código abierto y debe instalarse antes de generar la imagen. También hay un paquete de Python, pdfkitque sirve como interfaz (no le impide instalar el software principal usted mismo), pero no lo usaré.

wkhtmltoimagese puede llamar simplemente usando subprocess(o cualquier otro medio similar para ejecutar un programa externo en Python). También sería necesario enviar al disco el archivo HTML.

El código es el siguiente:

import subprocess


df.to_html('table2.html')
subprocess.call(
    'wkhtmltoimage -f png --width 0 table2.html table2.png', shell=True)

table_wkhtmltoimage

y su aspecto podría modificarse aún más con CSS de manera similar al otro enfoque.


norok2 avatar Jul 28 '2016 18:07 norok2

Aunque no estoy seguro de si este es el resultado que espera, puede guardar su DataFrame en png trazando el DataFrame con Seaborn Heatmap con anotaciones activadas, como esta:

http://stanford.edu/~mwaskom/software/seaborn/generated/seaborn.heatmap.html#seaborn.heatmap

Ejemplo de mapa de calor de Seaborn con anotaciones en

Funciona de inmediato con Pandas Dataframe. Puede ver este ejemplo: Trazar eficientemente una tabla en formato csv usando Python

Es posible que desees cambiar el mapa de colores para que muestre únicamente un fondo blanco.

Espero que esto ayude.

Editar: Aquí hay un fragmento que hace esto:

import matplotlib
import seaborn as sns

def save_df_as_image(df, path):
    # Set background to white
    norm = matplotlib.colors.Normalize(-1,1)
    colors = [[norm(-1.0), "white"],
            [norm( 1.0), "white"]]
    cmap = matplotlib.colors.LinearSegmentedColormap.from_list("", colors)
    # Make plot
    plot = sns.heatmap(df, annot=True, cmap=cmap, cbar=False)
    fig = plot.get_figure()
    fig.savefig(path)
jcdoming avatar Feb 25 '2016 19:02 jcdoming

La solución de @bunji me funciona, pero las opciones predeterminadas no siempre dan un buen resultado. Agregué algún parámetro útil para modificar la apariencia de la tabla.

import pandas as pd
import matplotlib.pyplot as plt
from pandas.plotting import table
import numpy as np

dates = pd.date_range('20130101',periods=6)
df = pd.DataFrame(np.random.randn(6,4),index=dates,columns=list('ABCD'))

df.index = [item.strftime('%Y-%m-%d') for item in df.index] # Format date

fig, ax = plt.subplots(figsize=(12, 2)) # set size frame
ax.xaxis.set_visible(False)  # hide the x axis
ax.yaxis.set_visible(False)  # hide the y axis
ax.set_frame_on(False)  # no visible frame, uncomment if size is ok
tabla = table(ax, df, loc='upper right', colWidths=[0.17]*len(df.columns))  # where df is your data frame
tabla.auto_set_font_size(False) # Activate set fontsize manually
tabla.set_fontsize(12) # if ++fontsize is necessary ++colWidths
tabla.scale(1.2, 1.2) # change size table
plt.savefig('table.png', transparent=True)

El resultado: Mesa

jrovegno avatar Apr 28 '2016 02:04 jrovegno