木星笔记本显示两个熊猫表并排

我有两个熊猫数据框,我想把它们放在 Jupyter 的笔记本上。

比如:

display(df1)
display(df2)

把它们一个接一个地展示出来:

enter image description here

我希望在第一个数据框的右边有第二个数据框。有 一个类似的问题,但它看起来有一个人是满意的,要么把它们合并到一个数据框架,显示它们之间的差异。

这对我没用。在我的例子中,数据框架可以表示完全不同的(非可比元素) ,它们的大小可以不同。因此,我的主要目标是节省空间。

111138 次浏览

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

我的解决方案只是在 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)

Output

最后,我编写了一个函数来实现以下功能: [更新: 根据建议添加标题(thnx@Antony _ Hatchkins et al。)]

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

以下是我前几天偶然发现的杰克•范德普拉斯(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

pandas 0.17.1开始,可以用 大熊猫造型方法直接修改 DataFrames 的可视化

要并排显示两个 DataFrames,必须按照 回答中的建议使用带参数 "style='display:inline'"set_table_attributes。这将返回两个 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)

aligned dataframes pandas styler with caption

这为@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)

enter image description here

我最后用的是 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)))

Gibbone 的回答对我很有用!如果您希望表之间有额外的空间,请转到他提出的代码,并将此 "\xa0\xa0\xa0"添加到下面的代码行中。

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

结合 Gibbone (设置样式和标题)和 stevi (添加空格)的方法,我制作了我的函数版本,它将熊猫的数据框并排输出为表:

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

产出:

enter image description here

扩展 Antony 的答案如果您想要将表的可视化限制为按行排列的一定数量的块,那么可以使用 maxTables 变量。enter image description here

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)

我决定为 Yasin 优雅的回答添加一些额外的功能,其中可以同时选择 protocol 还有行的数量; 然后将任何额外的 dfs 添加到底部。 此外,还可以选择填充网格的顺序(只需根据需要将 fill 关键字更改为‘ Protocol’或‘ 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)

测试输出

@ 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

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)决定了表之间的垂直间距。