Jupyter notebook muestra dos mesas pandas una al lado de la otra

94

Tengo dos marcos de datos de pandas y me gustaría mostrarlos en el cuaderno Jupyter.

Haciendo algo como:

display(df1)
display(df2)

Los muestra uno debajo del otro:

ingrese la descripción de la imagen aquí

Me gustaría tener un segundo marco de datos a la derecha del primero. Hay una pregunta similar , pero parece que una persona está satisfecha con fusionarlos en un marco de datos o mostrar la diferencia entre ellos.

Esto no funcionará para mí. En mi caso, los marcos de datos pueden representar elementos completamente diferentes (elementos no comparables) y el tamaño de ellos puede ser diferente. Por tanto, mi principal objetivo es ahorrar espacio.

Salvador Dalí
fuente
Publiqué la solución de Jake Vanderplas. Buen código limpio.
Privado

Respuestas:

85

Puede anular el CSS del código de salida. Utiliza flex-direction: columnpor defecto. Intente cambiarlo a en su rowlugar. He aquí un ejemplo:

import pandas as pd
import numpy as np
from IPython.display import display, HTML

CSS = """
.output {
    flex-direction: row;
}
"""

HTML('<style>{}</style>'.format(CSS))

Imagen de Jupyter

Por supuesto, puede personalizar aún más el CSS como desee.

Si desea apuntar solo a la salida de una celda, intente usar el :nth-child()selector. Por ejemplo, este código modificará el CSS de la salida de solo la quinta celda en el cuaderno:

CSS = """
div.cell:nth-child(5) .output {
    flex-direction: row;
}
"""
zarak
fuente
5
Esta solución afecta a todas las celdas, ¿cómo puedo hacer esto solo para una celda?
jrovegno
2
@jrovegno Actualicé mi respuesta para incluir la información que solicitó.
zarak
1
@ntg Debe asegurarse de que la línea HTML('<style>{}</style>'.format(CSS))sea ​​la última línea de la celda (y no olvide usar el selector nth-child). Sin embargo, esto puede causar problemas con el formato, por lo que su solución es mejor. (+1)
zarak
1
@zarak Gracias por las palabras amables :) En su solución, puede mostrar (HTML ('<style> {} </style>' .format (CSS))) en lugar de HTML ('<style> {} </ estilo> '. formato (CSS)). Entonces puede estar en cualquier lugar. Sin embargo, todavía tenía el problema con la celda n (es decir, si copio
pego
4
HTML('<style>.output {flex-direction: row;}</style>')por simplicidad
Thomas Matthew
114

Terminé escribiendo una función que puede hacer esto:

from IPython.display import display_html
def display_side_by_side(*args):
    html_str=''
    for df in args:
        html_str+=df.to_html()
    display_html(html_str.replace('table','table style="display:inline"'),raw=True)

Uso de ejemplo:

df1 = pd.DataFrame(np.arange(12).reshape((3,4)),columns=['A','B','C','D',])
df2 = pd.DataFrame(np.arange(16).reshape((4,4)),columns=['A','B','C','D',])
display_side_by_side(df1,df2,df1)

ingrese la descripción de la imagen aquí

ntg
fuente
Esto es realmente genial, gracias. ¿Qué tan fácil sería agregar el nombre del marco de datos encima de cada salida?
Ricky McMaster
1
Habría dos problemas: 1. saber los nombres de los marcos de datos está fuera del alcance en mi humilde opinión stackoverflow.com/questions/2749796/… pero puede hacer stackoverflow.com/questions/218616/… , o pasarlos como parámetros) 2. Usted necesitaría html adicional y está abierto / depende de usted qué hacer ... aquí hay un ejemplo básico de cómo podría verse esta parte: i.stack.imgur.com/mIVsD.png
ntg
Gracias por su respuesta, le agregué encabezados de una manera similar a lo que describió en su último comentario.
Antony Hatchkins
Increíble respuesta. Esto es lo que también estoy buscando. Todavía estoy aprendiendo a manejarlo, así que quiero saber: 1) ¿Por qué usaste en *argslugar de solo df? ¿Es porque puede tener múltiples entradas con *args? 2) ¿Qué parte de su función hace que el segundo gl y el subsiguiente se agreguen a la derecha del primero en lugar de debajo de él? ¿Es la 'table style="display:inline"'parte? Gracias de nuevo
Bowen Liu
1
¡Gracias por tu gran solución! Si desea aplicar estilo a sus marcos de datos antes de mostrarlos, la entrada será Stylers, no DataFrames. En este caso, utilice en html_str+=df.render()lugar de html_str+=df.to_html().
Martin Becker
35

A partir de pandas 0.17.1la visualización de DataFrames se puede modificar directamente con métodos de estilo pandas

Para mostrar dos DataFrames uno al lado del otro, debe usar set_table_attributesel argumento "style='display:inline'"como se sugiere en ntg answer . Esto devolverá dos Stylerobjetos. Para mostrar los marcos de datos alineados, simplemente pase su representación HTML combinada a través deldisplay_html método de IPython.

Con este método también es más fácil agregar otras opciones de estilo. A continuación, se explica cómo agregar un título, como se solicita aquí :

import numpy as np
import pandas as pd   
from IPython.display import display_html 

df1 = pd.DataFrame(np.arange(12).reshape((3,4)),columns=['A','B','C','D',])
df2 = pd.DataFrame(np.arange(16).reshape((4,4)),columns=['A','B','C','D',])

df1_styler = df1.style.set_table_attributes("style='display:inline'").set_caption('Caption table 1')
df2_styler = df2.style.set_table_attributes("style='display:inline'").set_caption('Caption table 2')

display_html(df1_styler._repr_html_()+df2_styler._repr_html_(), raw=True)

marcos de datos alineados pandas styler con título

gibbone
fuente
15

Combinando enfoques de gibbone (para establecer estilos y subtítulos) y stevi (agregando espacio) hice mi versión de la función, que genera marcos de datos de pandas como tablas una al lado de la otra:

from IPython.core.display import display, HTML

def display_side_by_side(dfs:list, captions:list):
    """Display tables side by side to save vertical space
    Input:
        dfs: list of pandas.DataFrame
        captions: list of table captions
    """
    output = ""
    combined = dict(zip(captions, dfs))
    for caption, df in combined.items():
        output += df.style.set_table_attributes("style='display:inline'").set_caption(caption)._repr_html_()
        output += "\xa0\xa0\xa0"
    display(HTML(output))

Uso:

display_side_by_side([df1, df2, df3], ['caption1', 'caption2', 'caption3'])

Salida:

ingrese la descripción de la imagen aquí

Anton Golubev
fuente
11

Aquí está la solución de Jake Vanderplas que encontré el otro día:

import numpy as np
import pandas as pd

class display(object):
    """Display HTML representation of multiple objects"""
    template = """<div style="float: left; padding: 10px;">
    <p style='font-family:"Courier New", Courier, monospace'>{0}</p>{1}
    </div>"""

    def __init__(self, *args):
        self.args = args

    def _repr_html_(self):
        return '\n'.join(self.template.format(a, eval(a)._repr_html_())
                     for a in self.args)

    def __repr__(self):
       return '\n\n'.join(a + '\n' + repr(eval(a))
                       for a in self.args)

Crédito: https://github.com/jakevdp/PythonDataScienceHandbook/blob/master/notebooks/03.08-Aggregation-and-Grouping.ipynb

Privado
fuente
1
¿Podría explicar esta respuesta? Jake VanderPlas no lo ha explicado en su sitio web. Esta es la única solución que imprime el nombre del conjunto de datos en la parte superior.
Gaurav Singhal
¿Que quieres saber?
Privado
Puede ser una descripción de todas las funciones / cómo funcionan, cómo se llaman, etc., para que los programadores novatos de Python puedan entenderlo correctamente.
Gaurav Singhal
10

Mi solución simplemente crea una tabla en HTML sin ningún truco de CSS y la genera:

import pandas as pd
from IPython.display import display,HTML

def multi_column_df_display(list_dfs, cols=3):
    html_table = "<table style='width:100%; border:0px'>{content}</table>"
    html_row = "<tr style='border:0px'>{content}</tr>"
    html_cell = "<td style='width:{width}%;vertical-align:top;border:0px'>{{content}}</td>"
    html_cell = html_cell.format(width=100/cols)

    cells = [ html_cell.format(content=df.to_html()) for df in list_dfs ]
    cells += (cols - (len(list_dfs)%cols)) * [html_cell.format(content="")] # pad
    rows = [ html_row.format(content="".join(cells[i:i+cols])) for i in range(0,len(cells),cols)]
    display(HTML(html_table.format(content="".join(rows))))

list_dfs = []
list_dfs.append( pd.DataFrame(2*[{"x":"hello"}]) )
list_dfs.append( pd.DataFrame(2*[{"x":"world"}]) )
multi_column_df_display(2*list_dfs)

Salida

Yasin Zähringer
fuente
9

Esto agrega encabezados a la respuesta de @ nts:

from IPython.display import display_html

def mydisplay(dfs, names=[]):
    html_str = ''
    if names:
        html_str += ('<tr>' + 
                     ''.join(f'<td style="text-align:center">{name}</td>' for name in names) + 
                     '</tr>')
    html_str += ('<tr>' + 
                 ''.join(f'<td style="vertical-align:top"> {df.to_html(index=False)}</td>' 
                         for df in dfs) + 
                 '</tr>')
    html_str = f'<table>{html_str}</table>'
    html_str = html_str.replace('table','table style="display:inline"')
    display_html(html_str, raw=True)

ingrese la descripción de la imagen aquí

Antony Hatchkins
fuente
Esto parece muy útil, pero me da un problema. Porque mydisplay((df1,df2))solo da en df.to_html(index=False) df.to_html(index=False)lugar del contenido del marco de datos. Además, hay un signo '}' adicional en f'string '.
Algo no relacionado, pero ¿es posible modificar su función para que el código de la salida de la celda esté oculto?
alpenmilch411
1
@ alpenmilch411 ver la extensión "Ocultar entrada"
Antony Hatchkins
¿Alguna idea de cómo agregar un 'max_rows' a esto?
Tickon
2

Terminé usando HBOX

import ipywidgets as ipyw

def get_html_table(target_df, title):
    df_style = target_df.style.set_table_attributes("style='border:2px solid;font-size:10px;margin:10px'").set_caption(title)
    return df_style._repr_html_()

df_2_html_table = get_html_table(df_2, 'Data from Google Sheet')
df_4_html_table = get_html_table(df_4, 'Data from Jira')
ipyw.HBox((ipyw.HTML(df_2_html_table),ipyw.HTML(df_4_html_table)))
Dinis Cruz
fuente
2

¡La respuesta de Gibbone funcionó para mí! Si desea espacio adicional entre las tablas, vaya al código que propuso y agréguelo "\xa0\xa0\xa0"a la siguiente línea de código.

display_html(df1_styler._repr_html_()+"\xa0\xa0\xa0"+df2_styler._repr_html_(), raw=True)
Stevi
fuente
2

Decidí agregar alguna funcionalidad adicional a la elegante respuesta de Yasin, donde uno puede elegir tanto el número de columnas como de filas; los dfs adicionales se agregan al final. Además, uno puede elegir en qué orden llenar la cuadrícula (simplemente cambie la palabra clave de relleno a 'cols' o 'filas' según sea necesario)

import pandas as pd
from IPython.display import display,HTML

def grid_df_display(list_dfs, rows = 2, cols=3, fill = 'cols'):
    html_table = "<table style='width:100%; border:0px'>{content}</table>"
    html_row = "<tr style='border:0px'>{content}</tr>"
    html_cell = "<td style='width:{width}%;vertical-align:top;border:0px'>{{content}}</td>"
    html_cell = html_cell.format(width=100/cols)

    cells = [ html_cell.format(content=df.to_html()) for df in list_dfs[:rows*cols] ]
    cells += cols * [html_cell.format(content="")] # pad

    if fill == 'rows': #fill in rows first (first row: 0,1,2,... col-1)
        grid = [ html_row.format(content="".join(cells[i:i+cols])) for i in range(0,rows*cols,cols)]

    if fill == 'cols': #fill columns first (first column: 0,1,2,..., rows-1)
        grid = [ html_row.format(content="".join(cells[i:rows*cols:rows])) for i in range(0,rows)]

    display(HTML(html_table.format(content="".join(grid))))

    #add extra dfs to bottom
    [display(list_dfs[i]) for i in range(rows*cols,len(list_dfs))]

list_dfs = []
list_dfs.extend((pd.DataFrame(2*[{"x":"hello"}]), 
             pd.DataFrame(2*[{"x":"world"}]), 
             pd.DataFrame(2*[{"x":"gdbye"}])))

grid_df_display(3*list_dfs)

salida de prueba

Martino Schröder
fuente
0

Extensión de la respuesta de antony Si desea limitar la visualización de tablas a un número de bloques por fila, use la variable maxTables.ingrese la descripción de la imagen aquí

def mydisplay(dfs, names=[]):

    count = 0
    maxTables = 6

    if not names:
        names = [x for x in range(len(dfs))]

    html_str = ''
    html_th = ''
    html_td = ''

    for df, name in zip(dfs, names):
        if count <= (maxTables):
            html_th += (''.join(f'<th style="text-align:center">{name}</th>'))
            html_td += (''.join(f'<td style="vertical-align:top"> {df.to_html(index=False)}</td>'))
            count += 1
        else:
            html_str += f'<tr>{html_th}</tr><tr>{html_td}</tr>'
            html_th = f'<th style="text-align:center">{name}</th>'
            html_td = f'<td style="vertical-align:top"> {df.to_html(index=False)}</td>'
            count = 0


    if count != 0:
        html_str += f'<tr>{html_th}</tr><tr>{html_td}</tr>'


    html_str += f'<table>{html_str}</table>'
    html_str = html_str.replace('table','table style="display:inline"')
    display_html(html_str, raw=True)
Arzanico
fuente