Le bloc-notes Jupyter affiche deux tables de pandas côte à côte

94

J'ai deux dataframes pandas et je voudrais les afficher dans le notebook Jupyter.

Faire quelque chose comme:

display(df1)
display(df2)

Les montre les uns sous les autres:

entrez la description de l'image ici

Je voudrais avoir un deuxième dataframe à droite du premier. Il y a une question similaire , mais il semble qu'une personne se contente soit de les fusionner dans une seule trame de données, soit de montrer la différence entre elles.

Cela ne fonctionnera pas pour moi. Dans mon cas, les dataframes peuvent représenter des éléments complètement différents (éléments non comparables) et leur taille peut être différente. Mon objectif principal est donc d'économiser de l'espace.

Salvador Dali
la source
J'ai posté la solution de Jake Vanderplas. Beau code propre.
Privé le

Réponses:

85

Vous pouvez remplacer le CSS du code de sortie. Il utilise flex-direction: columnpar défaut. Essayez de le rowremplacer par. Voici un exemple:

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

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

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

Image de Jupyter

Vous pouvez, bien sûr, personnaliser davantage le CSS comme vous le souhaitez.

Si vous souhaitez cibler la sortie d'une seule cellule, essayez d'utiliser le :nth-child()sélecteur. Par exemple, ce code modifiera le CSS de la sortie de seulement la 5ème cellule du notebook:

CSS = """
div.cell:nth-child(5) .output {
    flex-direction: row;
}
"""
zarak
la source
5
Cette solution affecte toutes les cellules, comment puis-je faire cela pour une seule cellule?
jrovegno
2
@jrovegno J'ai mis à jour ma réponse pour inclure les informations que vous avez demandées.
zarak
1
@ntg Vous devez vous assurer que la ligne HTML('<style>{}</style>'.format(CSS))est la dernière ligne de la cellule (et n'oubliez pas d'utiliser le sélecteur nth-child). Cependant, cela peut entraîner des problèmes de formatage, donc votre solution est meilleure. (+1)
zarak
1
@zarak Merci pour les mots aimables :) Dans votre solution, vous pouvez avoir display (HTML ('<style> {} </style>' .format (CSS))) au lieu de HTML ('<style> {} </ style> '. format (CSS)). Ensuite, cela peut être n'importe où. J'ai toujours eu le problème avec la nième cellule (ce qui signifie que si je copie coller, n pourrait changer)
ntg
4
HTML('<style>.output {flex-direction: row;}</style>')par souci de simplicité
Thomas Matthew
114

J'ai fini par écrire une fonction qui peut faire ceci:

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)

Exemple d'utilisation:

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)

entrez la description de l'image ici

ntg
la source
C'est vraiment super, merci. Dans quelle mesure serait-il facile ou non d'ajouter le nom du bloc de données au-dessus de chaque sortie, pensez-vous?
Ricky McMaster
1
Il y aurait deux problèmes: 1. connaître les noms des dataframes est hors de portée à mon humble avis stackoverflow.com/questions/2749796/... mais peut faire stackoverflow.com/questions/218616/… , ou les passer en tant que paramètres) 2. Vous aurait besoin de html supplémentaire et de son ouverture / à vous de savoir quoi faire ... voici un exemple de base de ce à quoi cette partie pourrait ressembler: i.stack.imgur.com/mIVsD.png
ntg
Merci pour votre réponse, j'y ai ajouté des en-têtes d'une manière similaire à ce que vous avez décrit dans votre dernier commentaire.
Antony Hatchkins
Réponse incroyable. C'est aussi ce que je recherche. J'apprends toujours mon chemin, donc je veux savoir: 1) Pourquoi avez-vous utilisé *argsau lieu de juste df? Est-ce parce que vous pouvez avoir plusieurs entrées avec *args? 2) Quelle partie de votre fonction fait que le 2ème df et les suivants s'ajoutent à droite du premier au lieu de le faire en dessous? Est-ce la 'table style="display:inline"'partie? Merci encore
Bowen Liu
1
Merci pour votre excellente solution! Si vous souhaitez styliser vos dataframes avant de les afficher, l'entrée sera Stylers, pas DataFrames. Dans ce cas, utilisez à la html_str+=df.render()place de html_str+=df.to_html().
Martin Becker
35

À partir de pandas 0.17.1la visualisation des DataFrames peuvent être directement modifiés avec les méthodes de style pandas

Pour afficher deux DataFrames côte à côte, vous devez utiliser set_table_attributesavec l'argument "style='display:inline'"comme suggéré dans ntg answer . Cela renverra deux Stylerobjets. Pour afficher les dataframes alignés, transmettez simplement leur représentation HTML jointe via la display_htmlméthode d'IPython.

Avec cette méthode, il est également plus facile d'ajouter d'autres options de style. Voici comment ajouter une légende, comme demandé ici :

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)

styler pandas de dataframes aligné avec légende

gibbone
la source
15

En combinant les approches de gibbone (pour définir les styles et les légendes) et stevi (ajouter de l'espace), j'ai créé ma version de function, qui produit des cadres de données pandas sous forme de tableaux côte à côte:

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

Usage:

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

Production:

entrez la description de l'image ici

Anton Golubev
la source
11

Voici la solution de Jake Vanderplas que je suis tombée l'autre jour:

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édit: https://github.com/jakevdp/PythonDataScienceHandbook/blob/master/notebooks/03.08-Aggregation-and-Grouping.ipynb

Privé
la source
1
pourriez-vous s'il vous plaît expliquer cette réponse. Jake VanderPlas ne l'a pas expliqué sur son site Internet. C'est la seule solution qui imprime le nom du jeu de données en haut.
Gaurav Singhal
Que veux-tu savoir?
Privé
Peut-être une description de toutes les fonctions / comment fonctionnent-elles, comment elles sont appelées ainsi de suite ... afin que les programmeurs python débutants puissent le comprendre correctement.
Gaurav Singhal
10

Ma solution construit simplement un tableau en HTML sans aucun hacks CSS et le produit:

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)

Production

Yasin Zähringer
la source
9

Cela ajoute des en-têtes à la réponse 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)

entrez la description de l'image ici

Antony Hatchkins
la source
Cela semble très utile, mais me pose un problème. For mydisplay((df1,df2))only donne à la df.to_html(index=False) df.to_html(index=False)place du contenu du dataframe. De plus, il y a un signe supplémentaire '}' à la f'string '.
Quelque peu sans rapport mais est-il possible de modifier votre fonction pour que le code de la sortie de la cellule soit masqué?
alpenmilch411
1
@ alpenmilch411 voir l'extension "Hide Input"
Antony Hatchkins
Une idée comment ajouter un 'max_rows' à cela?
Tickon le
2

J'ai fini par utiliser 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
la source
2

La réponse de Gibbone a fonctionné pour moi! Si vous voulez de l'espace supplémentaire entre les tables, accédez au code qu'il a proposé et ajoutez-le "\xa0\xa0\xa0"à la ligne de code suivante.

display_html(df1_styler._repr_html_()+"\xa0\xa0\xa0"+df2_styler._repr_html_(), raw=True)
stevi
la source
2

J'ai décidé d'ajouter des fonctionnalités supplémentaires à la réponse élégante de Yasin, où l'on peut choisir à la fois le nombre de cols et de lignes; tous les dfs supplémentaires sont ensuite ajoutés au bas. De plus, on peut choisir dans quel ordre remplir la grille (il suffit de changer le mot-clé de remplissage en 'cols' ou 'rows' selon les besoins)

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)

sortie de test

Martino Schröder
la source
0

Extension de la réponse d'Antony Si vous souhaitez limiter la visualisation des tableaux à quelques nombres de blocs par ligne, utilisez la variable maxTables.entrez la description de l'image ici

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
la source