Cómo guardar una tabla Pandas DataFrame como png
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.
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:
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 ix
indexador ahora ha quedado completamente obsoleto , por lo que deberíamos usarlo loc
en su lugar. Reemplazar:
df.ix[df.duplicated('first') , 'first'] = ''
con
df.loc[df.duplicated('first') , 'first'] = ''
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")
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 pip
paquete instalable, que le permitirá hacer todo utilizando el ecosistema Python. Una desventaja weasyprint
es 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 size
la 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)
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, pdfkit
que sirve como interfaz (no le impide instalar el software principal usted mismo), pero no lo usaré.
wkhtmltoimage
se 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)
y su aspecto podría modificarse aún más con CSS de manera similar al otro enfoque.
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
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)
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: