В ноутбуке Jupyter две таблицы pandas расположены рядом


96

У меня есть два фрейма данных pandas, и я хотел бы отобразить их в блокноте Jupyter.

Сделать что-то вроде:

display(df1)
display(df2)

Показывает их одно под другим:

введите описание изображения здесь

Я хотел бы иметь второй фрейм данных справа от первого. Есть аналогичный вопрос , но похоже, что там человека устраивает либо объединение их в один фрейм данных, либо отображение разницы между ними.

У меня это не сработает. В моем случае фреймы данных могут представлять совершенно разные (несопоставимые элементы), и их размер может быть разным. Таким образом, моя главная цель - сэкономить место.


Я опубликовал решение Джейка Вандерпласа. Хороший чистый код.
Рядовой

Ответы:


90

Вы можете переопределить CSS выходного кода. Используется flex-direction: columnпо умолчанию. rowВместо этого попробуйте изменить его на . Вот пример:

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

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

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

Изображение Jupyter

Конечно, вы можете настроить CSS дальше по своему желанию.

Если вы хотите настроить таргетинг только на одну ячейку, попробуйте использовать :nth-child()селектор. Например, этот код изменит CSS вывода только 5-й ячейки записной книжки:

CSS = """
div.cell:nth-child(5) .output {
    flex-direction: row;
}
"""

5
Это решение влияет на все ячейки. Как я могу сделать это только для одной ячейки?
jrovegno

2
@jrovegno Я обновил свой ответ, включив в него запрошенную вами информацию.
zarak

1
@ntg Вам нужно убедиться, что строка HTML('<style>{}</style>'.format(CSS))является последней строкой в ​​ячейке (и не забудьте использовать селектор nth-child). Однако это может вызвать проблемы с форматированием, поэтому ваше решение лучше. (+1)
zarak 06

1
@zarak Спасибо за добрые слова :) В вашем решении вы можете использовать display (HTML ('<style> {} </style>' .format (CSS))) вместо HTML ('<style> {} </ стиль> '. формат (CSS)). Тогда это может быть в любом месте. Однако у меня все еще была проблема с n-й ячейкой (то есть, если я
скопирую

4
HTML('<style>.output {flex-direction: row;}</style>')для простоты
Thomas Matthew

123

В итоге я написал функцию, которая может это сделать:

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)

Пример использования:

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)

введите описание изображения здесь


Это действительно здорово, спасибо. Как вы думаете, насколько легко или иначе было бы добавлять имя фрейма данных над каждым выводом?
Рики Макмастер

1
Могут возникнуть две проблемы: 1. зная, что имена фреймов данных выходят за рамки imho stackoverflow.com/questions/2749796/…, но можно сделать stackoverflow.com/questions/218616/… или передать их как параметры) 2. Вы потребуется дополнительный html и его открытый конец / решать
ntg

Спасибо за ваш ответ, я добавил к нему заголовки так же, как вы описали в своем последнем комментарии.
Энтони Хэтчкинс

Замечательный ответ. Это то, что я тоже ищу. Я все еще изучаю это, поэтому хочу знать: 1) Почему вы использовали *argsвместо просто df? Это потому, что у вас может быть несколько входов *args? 2) Какая часть вашей функции заставляет второй и последующие df добавлять справа от первого, а не под ним? Это 'table style="display:inline"'часть? Еще раз спасибо
Bowen Liu

1
Спасибо за отличное решение! Если вы хотите стилизовать фреймы данных перед их отображением, вводом будет Stylers, а не DataFrames. В этом случае используйте html_str+=df.render()вместо html_str+=df.to_html().
Мартин Беккер

37

Начиная с pandas 0.17.1визуализации DataFrames, можно напрямую изменять методы стилизации pandas.

Чтобы отобразить два DataFrames рядом, вы должны использовать set_table_attributesаргумент, "style='display:inline'"как предложено в ответе ntg . Это вернет два Stylerобъекта. Чтобы отобразить выровненные фреймы данных, просто передайте их объединенное HTML-представление через display_htmlметод из IPython.

С помощью этого метода также проще добавить другие варианты укладки. Вот как добавить подпись, как просили здесь :

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)

выровненные фреймы данных pandas styler с подписью


18

Комбинируя подходы gibbone (для установки стилей и подписей) и stevi (для добавления пространства), я создал свою версию функции, которая выводит фреймы данных pandas в виде таблиц рядом:

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

Применение:

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

Выход:

введите описание изображения здесь


11

Вот решение Джейка Вандерпласа, с которым я столкнулся буквально на днях:

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)

Кредит: https://github.com/jakevdp/PythonDataScienceHandbook/blob/master/notebooks/03.08-Aggregation-and-Grouping.ipynb


1
не могли бы вы объяснить этот ответ. Джейк Вандерплас не объяснил это на своем сайте. Это единственное решение, которое выводит имя набора данных вверху.
Gaurav Singhal

Что ты хочешь знать?
Рядовой

Может быть описание всех функций / как они работают, как они называются и т. Д., Чтобы новички в программировании на Python могли понять это правильно.
Гаурав Сингхал

10

Мое решение просто создает таблицу в HTML без каких-либо взломов CSS и выводит ее:

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)

Выход


9

Это добавляет заголовки к ответу @ 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)

введите описание изображения здесь


Это кажется очень полезным, но вызывает у меня проблему. Ибо mydisplay((df1,df2))только дает df.to_html(index=False) df.to_html(index=False)вместо содержимого фрейма данных. Кроме того, в строке f'string есть дополнительный знак '}'.

В некоторой степени не связано, но можно ли изменить вашу функцию так, чтобы код для вывода ячейки был скрыт?
alpenmilch411

1
@ alpenmilch411 см. расширение «Скрыть ввод»
Энтони Хэтчкинс

Есть идеи, как добавить к этому max_rows?
Tickon

Это тоже теряет мультииндексы, когда используются мультииндексированные фреймы данных.
Партибан Раджендран

2

В итоге я использовал 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)))

2

Ответ Гиббона сработал для меня! Если вам нужно дополнительное пространство между таблицами, перейдите к предложенному им коду и добавьте "\xa0\xa0\xa0"его в следующую строку кода.

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

2

Я решил добавить некоторые дополнительные функции к элегантному ответу Ясина, где можно выбрать как количество столбцов, так и количество строк; любые дополнительные файлы dfs затем добавляются внизу. Кроме того, можно выбрать, в каком порядке заполнять сетку (просто измените ключевое слово fill на 'cols' или 'rows' по мере необходимости)

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)

тестовый вывод


1

Код @zarak довольно мал, но влияет на макет всей записной книжки. Другие варианты для меня немного запутаны.

Я добавил к этому ответу чистый CSS, влияющий только на текущий вывод ячейки. Также вы можете добавить что-нибудь ниже или выше фреймов данных.

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

введите описание изображения здесь


0

Расширение ответа Антония. Если вы хотите ограничить де-визуализацию таблиц некоторым количеством блоков по строкам, используйте переменную maxTables.введите описание изображения здесь

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)

Это теряет мультииндексы при применении к мультииндексированному
фрейму данных
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.