Notatnik Jupyter wyświetla dwa stoły pandy obok siebie

96

Mam dwie pandy dataframe i chciałbym je wyświetlić w notatniku Jupyter.

Robię coś takiego:

display(df1)
display(df2)

Pokazuje je jeden pod drugim:

wprowadź opis obrazu tutaj

Chciałbym mieć drugą ramkę danych po prawej stronie pierwszej. Jest podobne pytanie , ale wygląda na to, że ktoś jest zadowolony z połączenia ich w jedną ramkę danych, aby pokazać różnicę między nimi.

To nie zadziała dla mnie. W moim przypadku ramki danych mogą reprezentować zupełnie inne (nieporównywalne elementy), a ich rozmiar może być inny. Dlatego moim głównym celem jest oszczędność miejsca.

Salvador Dali
źródło
Opublikowałem rozwiązanie Jake'a Vanderplasa. Ładny, czysty kod.
Prywatny

Odpowiedzi:

90

Możesz zastąpić CSS kodu wyjściowego. Używa flex-direction: columndomyślnie. Spróbuj zmienić to na row. Oto przykład:

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

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

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

Obraz Jupyter

Możesz oczywiście dalej dostosować CSS, jak chcesz.

Jeśli chcesz kierować tylko dane wyjściowe jednej komórki, spróbuj użyć :nth-child()selektora. Na przykład ten kod zmodyfikuje CSS danych wyjściowych tylko piątej komórki w notatniku:

CSS = """
div.cell:nth-child(5) .output {
    flex-direction: row;
}
"""
zarak
źródło
5
To rozwiązanie wpływa na wszystkie komórki, jak mogę to zrobić tylko dla jednej komórki?
jrovegno
2
@jrovegno Zaktualizowałem moją odpowiedź, aby zawierała informacje, o które prosiłeś.
zarak
1
@ntg Musisz upewnić się, że linia HTML('<style>{}</style>'.format(CSS))jest ostatnią linią w komórce (i nie zapomnij użyć selektora nth-child). Może to jednak powodować problemy z formatowaniem, więc rozwiązanie jest lepsze. (+1)
zarak
1
@zarak Dzięki za miłe słowa :) W swoim rozwiązaniu możesz mieć display (HTML ('<style> {} </style>' .format (CSS))) zamiast HTML ('<style> {} </ style> '. format (CSS)). Wtedy może być w dowolnym miejscu. Nadal miałem problem z n-tą komórką (co oznacza, że ​​jeśli skopiuję wklej, n może się zmienić)
ntg
4
HTML('<style>.output {flex-direction: row;}</style>')dla uproszczenia
Thomas Matthew
123

Skończyło się na napisaniu funkcji, która może to zrobić:

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)

Przykładowe użycie:

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)

wprowadź opis obrazu tutaj

ntg
źródło
To jest naprawdę świetne, dzięki. Jak myślisz, jak łatwe lub inne byłoby dodanie nazwy ramki danych nad każdym wyjściem?
Ricky McMaster
1
Wystąpiłyby dwa problemy: 1. wiedząc, że nazwy ramek danych są poza zakresem imho stackoverflow.com/questions/2749796/ ... ale można to zrobić stackoverflow.com/questions/218616/ ... lub przekazać je jako parametry) 2. Ty potrzebowałby dodatkowej html i otwartej zakończył / aż do tego, co zrobić ... oto przykład, w jaki sposób baza ta część może wyglądać: i.stack.imgur.com/mIVsD.png
NTG
Dziękuję za odpowiedź, dodałem do niego nagłówki w sposób podobny do tego, co opisałeś w swoim ostatnim komentarzu.
Antony Hatchkins
Niesamowita odpowiedź. Tego też szukam. Wciąż uczę się, jak to obejść, więc chcę wiedzieć: 1) Dlaczego użyłeś *argszamiast po prostu df? Czy to dlatego, że możesz mieć wiele danych wejściowych *args? 2) Która część twojej funkcji powoduje, że druga i kolejne df są dodawane po prawej stronie pierwszej zamiast poniżej niej? Czy to ta 'table style="display:inline"'część? Jeszcze raz dziękuję
Bowen Liu
1
Dzięki za świetne rozwiązanie! Jeśli chcesz stylizować ramki danych przed ich wyświetleniem, dane wejściowe będą Stylers, DataFramea nie s. W takim przypadku użyj html_str+=df.render()zamiast html_str+=df.to_html().
Martin Becker
38

Począwszy od pandas 0.17.1wizualizacji DataFrames można bezpośrednio modyfikować metodami stylizacji pand

Aby wyświetlić dwie ramki DataFrame obok siebie, należy użyć set_table_attributesargumentu "style='display:inline'"zgodnie z sugestią zawartą w odpowiedzi ntg . To zwróci dwa Stylerobiekty. Aby wyświetlić wyrównane ramki danych, po prostu przekaż ich połączoną reprezentację HTML za pomocą display_htmlmetody z IPython.

Dzięki tej metodzie łatwiej jest też dodać inne opcje stylizacji. Oto jak dodać podpis, zgodnie z wnioskiem o :

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)

wyrównany styler pandy dataframes z podpisem

gibbone
źródło
18

Łącząc podejście gibbone (do ustawiania stylów i podpisów) i stevi (dodawania spacji) stworzyłem moją wersję funkcji, która generuje ramki danych pandy jako tabele obok siebie:

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

Stosowanie:

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

Wynik:

wprowadź opis obrazu tutaj

Anton Golubev
źródło
11

Oto rozwiązanie Jake'a Vanderplasa, na które natknąłem się niedawno:

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)

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

Prywatny
źródło
1
czy mógłbyś wyjaśnić tę odpowiedź. Jake VanderPlas nie wyjaśnił tego na swojej stronie internetowej. Jest to jedyne rozwiązanie, które wyświetla nazwę zestawu danych na górze.
Gaurav Singhal
Co chcesz wiedzieć?
Prywatny
Może to być opis wszystkich funkcji / jak działają, jak się je nazywa i tak dalej ... aby początkujący programiści Pythona mogli to poprawnie zrozumieć.
Gaurav Singhal
10

Moje rozwiązanie po prostu buduje tabelę w HTML bez żadnych hacków CSS i wyświetla ją:

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)

Wynik

Yasin Zähringer
źródło
9

To dodaje nagłówki do odpowiedzi @ 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)

wprowadź opis obrazu tutaj

Antony Hatchkins
źródło
Wydaje się to bardzo przydatne, ale stwarza mi problem. Bo mydisplay((df1,df2))tylko podaje df.to_html(index=False) df.to_html(index=False)zamiast zawartości ramki danych. Ponadto na f'string znajduje się dodatkowy znak '}'.
Trochę niepowiązane, ale czy można zmodyfikować funkcję tak, aby kod wyjściowy komórki był ukryty?
alpenmilch411,
1
@ alpenmilch411 zobacz rozszerzenie „Hide Input”
Antony Hatchkins,
Masz pomysł, jak dodać do tego „max_rows”?
Tickon
To również powoduje utratę wielu indeksów, gdy używane są ramki danych z wieloma indeksami.
Parthiban Rajendran
2

Skończyło się na 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
źródło
2

Odpowiedź Gibbone'a zadziałała dla mnie! Jeśli chcesz uzyskać dodatkową przestrzeń między tabelami, przejdź do zaproponowanego przez niego kodu i dodaj go "\xa0\xa0\xa0"do poniższej linii kodu.

display_html(df1_styler._repr_html_()+"\xa0\xa0\xa0"+df2_styler._repr_html_(), raw=True)
stevi
źródło
2

Postanowiłem dodać dodatkową funkcjonalność do eleganckiej odpowiedzi Yasin, w której można wybrać zarówno liczbę kolumn, jak i wierszy; wszelkie dodatkowe pliki df są następnie dodawane na dole. Dodatkowo można wybrać kolejność wypełniania siatki (po prostu zmień słowo kluczowe fill na „cols” lub „rowery” w zależności od potrzeb)

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)

wyjście testowe

Martino Schröder
źródło
1

Kod @zarak jest dość mały, ale wpływa na układ całego notatnika. Inne opcje są dla mnie nieco kłopotliwe.

Dodałem do tej odpowiedzi kilka wyraźnych CSS wpływających tylko na bieżące wyjście komórki. Możesz także dodać cokolwiek poniżej lub powyżej ramek danych.

from ipywidgets import widgets, Layout
from IPython import display
import pandas as pd
import numpy as np

# sample data
df1 = pd.DataFrame(np.random.randn(8, 3))
df2 = pd.DataFrame(np.random.randn(8, 3))

# create output widgets
widget1 = widgets.Output()
widget2 = widgets.Output()

# render in output widgets
with widget1:
    display.display(df1.style.set_caption('First dataframe'))
    df1.info()
with widget2:
    display.display(df2.style.set_caption('Second dataframe'))
    df1.info()


# add some CSS styles to distribute free space
box_layout = Layout(display='flex',
                    flex_flow='row',
                    justify_content='space-around',
                    width='auto'
                   )
    
# create Horisontal Box container
hbox = widgets.HBox([widget1, widget2], layout=box_layout)

# render hbox
hbox

wprowadź opis obrazu tutaj

MSorro
źródło
0

Rozszerzenie odpowiedzi Antoniego Jeśli chcesz ograniczyć de wizualizację tabel do pewnej liczby bloków w wierszu, użyj zmiennej maxTables.wprowadź opis obrazu tutaj

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
źródło
Spowoduje to utratę wielu indeksów, gdy zostanie zastosowany na wielozindeksowanej ramce danych
Parthiban Rajendran