O caderno Jupyter exibe duas mesas de pandas lado a lado

94

Tenho dois dataframes de pandas e gostaria de exibi-los no bloco de notas Jupyter.

Fazendo algo como:

display(df1)
display(df2)

Mostra um abaixo do outro:

insira a descrição da imagem aqui

Eu gostaria de ter um segundo dataframe à direita do primeiro. Há uma pergunta semelhante , mas parece que uma pessoa está satisfeita em mesclá-los em um dataframe ou mostrar a diferença entre eles.

Isso não vai funcionar para mim. No meu caso, os dataframes podem representar completamente diferentes (elementos não comparáveis) e o tamanho deles pode ser diferente. Portanto, meu principal objetivo é economizar espaço.

Salvador Dalí
fonte
Eu postei a solução de Jake Vanderplas. Bom código limpo.
Privado de

Respostas:

85

Você pode substituir o CSS do código de saída. Ele usa flex-direction: columnpor padrão. Em rowvez disso, tente mudar para . Aqui está um exemplo:

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

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

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

Imagem Jupyter

Você pode, é claro, personalizar o CSS conforme desejar.

Se você deseja direcionar apenas a saída de uma célula, tente usar o :nth-child()seletor. Por exemplo, este código modificará o CSS da saída de apenas a 5ª célula do notebook:

CSS = """
div.cell:nth-child(5) .output {
    flex-direction: row;
}
"""
Zarak
fonte
5
Esta solução afeta todas as células. Como posso fazer isso para apenas uma célula?
jrovegno
2
@jrovegno Atualizei minha resposta para incluir as informações que você solicitou.
zarak
1
@ntg Você precisa garantir que a linha HTML('<style>{}</style>'.format(CSS))seja a última linha na célula (e não se esqueça de usar o seletor n-ésimo). No entanto, isso pode causar problemas com a formatação, portanto, sua solução é melhor. (+1)
zarak
1
@zarak Thanx pelas palavras gentis :) Em sua solução, você pode ter display (HTML ('<style> {} </style>' .format (CSS))) em vez de HTML ('<style> {} </ estilo> '. formato (CSS)). Então, pode estar em qualquer lugar. Ainda tive o problema com a enésima célula (ou seja, se eu copiar e colar, n pode mudar)
ntg
4
HTML('<style>.output {flex-direction: row;}</style>')para simplificar
Thomas Matthew de
114

Acabei escrevendo uma função que pode fazer isso:

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)

Exemplo de uso:

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)

insira a descrição da imagem aqui

ntg
fonte
Isso é ótimo, obrigado. Você acha que seria fácil ou não adicionar o nome do quadro de dados acima de cada saída?
Ricky McMaster de
1
Haveria dois problemas: 1. saber os nomes dos dataframes está fora do escopo imho stackoverflow.com/questions/2749796/… mas pode fazer stackoverflow.com/questions/218616/… , ou passá-los como parâmetros) 2. Você precisaria de html extra e seu final aberto / você decide
ntg
Obrigado pela sua resposta, adicionei cabeçalhos a ele de maneira semelhante ao que você descreveu em seu último comentário.
Antony Hatchkins de
Resposta incrível. Isso é o que estou procurando também. Ainda estou aprendendo a contornar isso, então quero saber: 1) Por que você usou em *argsvez de apenas df? É porque você pode ter várias entradas com *args? 2) Qual parte de sua função faz com que o segundo df e os subseqüentes sejam adicionados à direita do primeiro em vez de abaixo dele? É a 'table style="display:inline"'parte? Obrigado novamente
Bowen Liu
1
Obrigado pela sua ótima solução! Se você quiser estilizar seus dataframes antes de exibi-los, a entrada será Stylers, não DataFrames. Nesse caso, use em html_str+=df.render()vez de html_str+=df.to_html().
Martin Becker
35

A partir pandas 0.17.1da visualização de DataFrames podem ser modificados diretamente com métodos de estilo pandas

Para exibir dois DataFrames lado a lado, você deve usar set_table_attributescom o argumento "style='display:inline'"sugerido na resposta ntg . Isso retornará dois Stylerobjetos. Para exibir os dataframes alinhados, basta passar sua representação HTML associada por meio do display_htmlmétodo do IPython.

Com esse método, também é mais fácil adicionar outras opções de estilo. Veja como adicionar uma legenda, conforme solicitado aqui :

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)

dataframes pandas styler alinhado com legenda

gibone
fonte
15

Combinando abordagens de gibbone (para definir estilos e legendas) e stevi (adicionar espaço), fiz minha versão da função, que produz dataframes de pandas como tabelas lado a lado:

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'])

Resultado:

insira a descrição da imagem aqui

Anton Golubev
fonte
11

Aqui está a solução de Jake Vanderplas que encontrei outro dia:

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
fonte
1
você poderia explicar esta resposta. Jake VanderPlas não explicou isso em seu site. Esta é a única solução que imprime o nome do conjunto de dados na parte superior.
Gaurav Singhal
O que você quer saber?
Privado de
Pode ser uma descrição de todas as funções / como funcionam, como são chamadas e assim por diante ... para que os programadores novatos em python possam entendê-las corretamente.
Gaurav Singhal
10

Minha solução apenas cria uma tabela em HTML sem nenhum hacks CSS e a produz:

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)

Resultado

Yasin Zähringer
fonte
9

Isso adiciona cabeçalhos à resposta 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)

insira a descrição da imagem aqui

Antony Hatchkins
fonte
Isso parece muito útil, mas me dá um problema. Pois mydisplay((df1,df2))apenas dá em df.to_html(index=False) df.to_html(index=False)vez do conteúdo do dataframe. Além disso, há um sinal extra '}' em f'string '.
Algo não relacionado, mas é possível modificar sua função de forma que o código para a saída da célula fique oculto?
alpenmilch411
1
@ alpenmilch411 consulte a extensão "Ocultar entrada"
Antony Hatchkins
Alguma idéia de como adicionar um 'max_rows' a isso?
Tickon
2

Acabei 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
fonte
2

A resposta de Gibbone funcionou para mim! Se você quiser espaço extra entre as tabelas, vá para o código que ele propôs e adicione "\xa0\xa0\xa0"-o à seguinte linha de código.

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

Decidi adicionar algumas funcionalidades extras à elegante resposta de Yasin, em que é possível escolher o número de colunas e linhas; quaisquer dfs extras são então adicionados à parte inferior. Além disso, pode-se escolher em que ordem preencher a grade (basta alterar a palavra-chave de preenchimento para 'cols' ou 'linhas' conforme necessário)

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)

saída de teste

Martino Schröder
fonte
0

Extensão da resposta de antony Se você quiser limitar a visualização das tabelas a alguns blocos por linha, use a variável maxTables.insira a descrição da imagem aqui

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
fonte