В ноутбуке 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.