Jupyter笔记本显示两个Pandas表并排放置

151

我有两个pandas数据框,我想在Jupyter笔记本中显示它们。

像这样做:


display(df1)
display(df2)

将它们一个接一个地显示:

输入图像说明

我希望在第一个数据框的右侧有第二个数据框。有一个类似的问题,但看起来那里的人要么满足于将它们合并为一个数据框,要么满足于显示它们之间的差异。

这对我不起作用。在我的情况下,数据框可以表示完全不同(不可比较的元素),它们的大小也可能不同。因此,我的主要目标是节省空间。


我发布了Jake Vanderplas的解决方案。代码很干净漂亮。 - Private
13个回答

198
我已经写出了一个可以实现这个功能的函数: [更新:根据建议添加了标题(感谢 @Antony_Hatchkins 等人)]
from IPython.display import display_html
from itertools import chain,cycle
def display_side_by_side(*args,titles=cycle([''])):
    html_str=''
    for df,title in zip(args, chain(titles,cycle(['</br>'])) ):
        html_str+='<th style="text-align:center"><td style="vertical-align:top">'
        html_str+=f'<h2 style="text-align: center;">{title}</h2>'
        html_str+=df.to_html().replace('table','table style="display:inline"')
        html_str+='</td></th>'
    display_html(html_str,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, titles=['Foo','Foo Bar']) #we left 3rd empty...

enter image description here


这真的很棒,谢谢。您认为在每个输出上方添加数据框名称有多容易或不容易? - Ricky McMaster
2
感谢您提供的出色解决方案!如果您想在显示数据框之前对其进行样式设置,则输入应为“Styler”,而不是“DataFrame”。在这种情况下,请使用html_str+=df.render()而不是html_str+=df.to_html() - Martin Becker
2
由于某些原因,这在JupyterLab 3.0.11中无法正常工作。也许是因为JupyterLab使用与Jupyter Classic NB不同的客户端渲染引擎?我刚刚尝试在Jupyter Classic NB中运行上面完全相同的代码,该代码是从JupyterLab的v3.0.11帮助菜单中启动的,以确保所有其他变量都相同。它像上面展示的那样完美地显示出来。我正在Python v3.7.10上运行iPython 7.25.0。有趣!我还不完全理解为什么JupyterLab无法呈现HTML。有人知道原因吗? - Rich Lysakowski PhD
1
@RichLysakowskiPhD 我不知道为什么,但是这个没有标题的变体在 JupyterLab(v3.1.11)中可以工作:https://newbedev.com/jupyter-notebook-display-two-pandas-tables-side-by-side - Wayne
1
一个微调,使标题居中在它们的数据框上:html_str+=f'<h2 style="text-align: center;">{title}</h2>' - jgreve
显示剩余5条评论

107

您可以覆盖输出代码的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 image

如果你想自定义更多的CSS样式,可以按照自己的意愿进行操作。

如果只想针对一个单元格的输出内容进行修改,可以尝试使用:nth-child()选择器。例如,下面这段代码将修改笔记本中第5个单元格的输出内容的CSS:

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

7
这个解决方案会影响到所有的单元格,我该怎么只让它影响一个单元格? - jrovegno
3
@jrovegno,我更新了我的回答以包含您请求的信息。 - zarak
2
@ntg 你需要确保行 HTML('<style>{}</style>'.format(CSS)) 是单元格中的最后一行(不要忘记使用nth-child选择器)。然而,这可能会导致格式方面的问题,所以你的解决方案更好。 (+1) - zarak
2
@zarak 谢谢你的赞美 :) 在你的解决方案中,你可以使用 display(HTML('<style>{}</style>'.format(CSS))) 代替 HTML('<style>{}</style>'.format(CSS))。这样它就可以放在任何地方了。不过我仍然有第n个单元格的问题(也就是说,如果我复制粘贴,n可能会改变)。 - ntg
4
HTML('<style>.output {flex-direction: row;}</style>') 为简单起见 - Thomas Matthew
显示剩余4条评论

73

自从 pandas 0.17.1 开始,可以使用 pandas 样式方法直接修改 DataFrame 的可视化效果

要将两个 DataFrame 并排显示,您必须使用 set_table_attributes 方法,并使用参数 "style='display:inline'",如 ntg的回答中建议的那样。这将返回两个 Styler 对象。要显示对齐的数据框,只需通过 IPython 的 display_html 方法传递它们连接的 HTML 表示即可。

使用此方法还可以更轻松地添加其他样式选项。以下是如何添加标题,如此处所请求的:

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样式对齐数据框


1
没注意到,这似乎非常好,可以在更多情况下提供帮助,例如添加颜色等。(+1) - ntg
6
@gibbone 有没有办法指定表格之间的间距? - a11

37

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

输出:

在此输入图像描述


14

我的解决方案只是建立一个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)

输出


14

enter image description here这是 @Anton Golubev 引入的 display_side_by_side() 函数的另一种变体,它结合了 gibbone(用于设置样式和标题)和 stevi(添加空白)。我在运行时添加了一个额外的参数来改变表格之间的间距。

from IPython.core.display import display, HTML

def display_side_by_side(dfs:list, captions:list, tablespacing=5):
    """Display tables side by side to save vertical space
    Input:
        dfs: list of pandas.DataFrame
        captions: list of table captions
    """
    output = ""
    for (caption, df) in zip(captions, dfs):
        output += df.style.set_table_attributes("style='display:inline'").set_caption(caption)._repr_html_()
        output += tablespacing * "\xa0"
    display(HTML(output))
    
display_side_by_side([df1, df2, df3], ['caption1', 'caption2', 'caption3'])

表格间隔(默认参数值为5,此处显示为5)决定了表格之间的垂直间距。


非常方便,谢谢。 - Aristide
1
非常喜欢这个,有什么想法为什么在vscode中表格无法顶部对齐?如果它们具有相同数量的行,则看起来很棒,但如果行数不同,则最终会居中垂直对齐。 - pwb2103

12

这个方案在 @nts 的回答基础上增加了(可选的)标题、索引和 Series 支持:

from IPython.display import display_html

def mydisplay(dfs, names=[], index=False):
    def to_df(x):
        if isinstance(x, pd.Series):
            return pd.DataFrame(x)
        else:
            return x
    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"> {to_df(df).to_html(index=index)}</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 字符串中有额外的 '}' 符号。 - user8864088
有点不相关,但是您是否可以修改您的函数,以便隐藏单元格输出的代码? - alpenmilch411
1
@ alpenmilch411 请查看“Hide Input”扩展。 - Antony Hatchkins
有什么想法可以在这里添加'max_rows'吗? - Tickon
你的代码实际上并没有运行。我收到了一个错误信息:“NameError: name 'bases' is not defined”。虽然函数已经定义,但是你该如何使用它呢?能否改进一下你的回答,解释一下如何调用这个函数,以便你的回答可以独立运行?谢谢。 - Rich Lysakowski PhD
显示剩余2条评论

12

我最近发现了Jake Vanderplas的解决方案,如下:

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
请问您能否解释一下这个答案?Jake VanderPlas在他的网站上没有解释过。这是唯一一个在顶部打印数据集名称的解决方案。 - Gaurav Singhal
你想知道什么? - Private
可能是所有函数的描述/它们如何工作、如何调用等等...以便新手Python程序员可以正确理解。 - Gaurav Singhal
当使用Python进行交互式操作并且想要显示输入行的最终结果时,它会调用__repr__()方法并输出返回的字符串。为了仍然能够使用它,这个display对象有一个__repr__()方法,它只是简单地输出每个对象的repr(),并用换行符分隔。为了支持在HTML中呈现结果,Jupyter Notebook有一个类似的方法_repr_html_(),它将首选并首先调用(如果可用)。该对象也定义了该方法,其中包含一小段HTML代码,可以将每个对象的_repr_html_()并排显示。 - Christian Hudon
+1 这是唯一对我有效的解决方案,但我稍微改变了它,使用 **kwargs 而不是使用 *argseval 评估输入。https://gist.github.com/net-raider/c9986ffa84cbfa106f91be3987953c83 - Net_Raider

9

@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

enter image description here


1
这太棒了。我喜欢提供有关数据框的其他元数据选项。 - Rich Lysakowski PhD
1
这是纯粹的天才,因为它也可以与matplotlib对象一起使用:我正在使用它在左侧打印pandas表格和右侧的图表! - erickfis
1
我喜欢这个!这个答案不需要对内容进行更改,所以你可以直接传入你最奇怪的数据框。 - tnwei

4

Gibbone的答案对我有用。如果您想要在表格之间增加额外的空间,请查看他提供的代码,并将"\xa0\xa0\xa0"添加到以下代码行中。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接