Pandas, la bibliothèque Python populaire pour la manipulation et l'analyse
de données, offre une grande flexibilité grâce à ses nombreuses
fonctionnalités intégrées. Cependant, il y a des moments où vous pourriez
souhaiter ajouter des fonctionnalités spécifiques à votre domaine.
C'est là que la fonction
pd.api.extensions.register_dataframe_accessor
entre en jeu.
pd.api.extensions.register_dataframe_accessor
est une fonctionnalité avancée de Pandas qui vous permet d'ajouter des
méthodes et des attributs personnalisés à un DataFrame.
Le moteur Python utilisé sera CPython3 (à ce jour ce
moteur utilise PythonNet 2.5.x qui nécessite une
implémentation assez particulière dès lors que l'on veut manipuler des
objets .Net)
Note :
IronPython et PythonNet ont tous les
deux des avantages, l'un est natif .Net l'autre rend
possible l'utilisation de bibliothèques populaire Python. Autant
profiter des 2.
Le script dynamo est constitué de 2 nœuds :
-
un premier nœud Python qui affichera les DataTables et les
Graphiques dans un Winform
Note : l'affichage se fait au moyen d'un WebBrowser et la mise à jour du code html se fait par la mise à jour d'une propriété (getter et setter)
La classe nécessite d'être une .Net Classe (voir documentation PythonNet) -
Implémentation de l'attribut
__namespace__ -
Modification de la propriété HtmlText via
clrproperty(propriété accessible depuis le CLR)
-
un deuxième nœud Python où seront implémentées les méthodes
d'extension Pandas
-
Une méthode
Show()qui prend en arguments le nombre de lignes à afficher (si égal à 0 → affiche tout le Dataframe).
La méthode ne s'applique que sur un objet Dataframe, si vous avez un objet Series, il faudra au préalable le convertir avec la méthodeto_frame()
-
Une méthode
Plot()pour tracer quelques graphiques. Afin d'éviter de créer une méthode d'extension pour chaque méthode, celle-ci prend en argument le type de méthode Dataframe.plot sous forme de chaine de caractères.

Save()
pour sauvegarder le contenu HTML dans un fichier 💾.
-
Pour rappel avec pandas, l'alignement des données est
intrinsèque.
Cela signifie que vous pouvez passer une liste de dictionnaire avec des différentes clés , pandas fera l'alignement de colonnes (ajout de values NaN).
- A contrario d'une .Net DataTable (Microsoft.Data) ou d'un .Net DataFrame (Microsoft.Data.Analysis) un Dataframe pandas ne supporte pas complètement les objets .Net.
code python "dataframe extension"
import clr
import sys
import System
import uuid
clr.AddReference("System.Numerics")
#
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
import Autodesk.DesignScript.Geometry as DS
#import Revit API
clr.AddReference('RevitAPI')
import Autodesk
from Autodesk.Revit.DB import *
import Autodesk.Revit.DB as DB
#import specify namespace
from Autodesk.Revit.DB.Electrical import *
#import net library
from System import Array
from System.Collections.Generic import List, IList, Dictionary
#import transactionManager and DocumentManager (RevitServices is specific to Dynamo)
clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument
clr.AddReference('System.Drawing')
clr.AddReference('System.Windows.Forms')
import System.Drawing
import System.Windows.Forms
from System.Drawing import *
from System.Windows.Forms import *
import numpy as np
import pandas as pd
import webbrowser
import matplotlib.pyplot as plt
import io
import base64
import re
class MyForm(Form):
__namespace__ = "Dynamo.PandasViewer_" + uuid.uuid4().hex
#
init_htmlDf = """\
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=9">
<style>
table, th, td {font-size:10pt; border:1px solid black; border-collapse:collapse; text-align:left;}
th, td {padding: 5px;}
tr:nth-child(even) {background: #E0E0E0;}
tr:hover {background: silver; cursor: pointer;}
</style>
</head>
<body>
</body>
</html>
"""
def __init__(self):
super().__init__()
self.htmlDf = MyForm.init_htmlDf
self.InitializeComponent()
def InitializeComponent(self):
self._webBrowser1 = System.Windows.Forms.WebBrowser()
self.SuspendLayout()
#
# webBrowser1
#
self._webBrowser1.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right
self._webBrowser1.Dock = System.Windows.Forms.DockStyle.Fill
self._webBrowser1.Location = System.Drawing.Point(0, 0)
self._webBrowser1.ScriptErrorsSuppressed = True
self._webBrowser1.Name = "webBrowser1"
self._webBrowser1.Size = System.Drawing.Size(594, 528)
self._webBrowser1.TabIndex = 0
self._webBrowser1.DocumentText = self.htmlDf
self._webBrowser1.DocumentCompleted += self.WebBrowser1DocumentCompleted
#
# Form27
#
self.ClientSize = System.Drawing.Size(600, 540)
self.Controls.Add(self._webBrowser1)
self.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Show
self.SizeChanged += self.FormSizeChanged
self.Name = "Form27"
self.Text = "PandasViewer"
def WebBrowser1DocumentCompleted(self, sender, e):
self._webBrowser1.Document.Body.ScrollIntoView(False)
def FormSizeChanged(self, sender, e):
self._webBrowser1.Size = System.Drawing.Size.Subtract(self.ClientSize, System.Drawing.Size(10, 10))
self._webBrowser1.Document.Body.Width = self.ClientSize.Width - 10
#@property
def get_HtmlText(self):
#print(self.htmlDf)
return self.htmlDf
#@HtmlText.setter
def set_HtmlText(self, value):
self.htmlDf = value
self._webBrowser1.Document.OpenNew(True)
self._webBrowser1.Document.Write(self.htmlDf)
self._webBrowser1.Refresh()
#self.Activate()
HtmlText = clr.clrproperty(str, get_HtmlText, set_HtmlText)
def ResetHtml(self):
self.htmlDf = self.init_htmlDf
@pd.api.extensions.register_dataframe_accessor("viewer")
class HtmlAccessorOLD:
def __init__(self, df_obj):
self._obj = df_obj
#
def Show(self, n=0, label=""):
"""
convert DataFrame to Html and show it
"""
# get current html
htmlDf = viewerForm.htmlDf
# convert current dataFrame to html
if n == 0:
html_toAdd = self._obj.to_html()
else:
html_toAdd = self._obj.head(n).to_html()
html_toAdd = f"<p> {label}</p>" + html_toAdd
# add to the current html
htmlDf = re.sub(r"<\/body>", html_toAdd + r"<br><hr><br></body>", htmlDf)
# update the html in form
viewerForm.HtmlText = htmlDf
def Plot(self, method="plot()"):
"""
plot a DataFrame
type = 'bar', 'area', 'box',
"""
plt.figure()
s = io.BytesIO()
if method.startswith("."):
method = method[1:]
if not method.startswith("plot"):
method = "plot." + method
#
ax = eval("self._obj."+ method)
#
plt.savefig(s, format='png', bbox_inches="tight")
plt.close()
s = base64.b64encode(s.getvalue()).decode("utf-8").replace("\n", "")
#
htmlDf = viewerForm.htmlDf
html_toAdd = """<img align="left" src="data:image/png;base64,{0}">""".format(s)
#
# add to the current
htmlDf = re.sub(r"<\/body>", html_toAdd + r"<br clear=left><hr><br></body>", htmlDf)
#
# update the html in form
viewerForm.HtmlText = htmlDf
@pd.api.extensions.register_dataframe_accessor("viewer")
class HtmlAccessor:
def __init__(self, df_obj):
self._obj = df_obj
def _get_base_html(self):
html_content = getattr(viewerForm, "htmlDf", "")
if not html_content or "</body>" not in html_content:
return "<html><body></body></html>"
return html_content
def Show(self, n=0, label=""):
html_df = self._get_base_html()
df_to_show = self._obj if n == 0 else self._obj.head(n)
html_to_add = df_to_show.to_html()
if label:
html_to_add = f"<p><b>{label}</b></p>" + html_to_add
updated_html = re.sub(
r"</body>", html_to_add + r"<br><hr><br></body>", html_df, flags=re.I
)
viewerForm.HtmlText = updated_html
viewerForm.htmlDf = updated_html
def Plot(self, method="plot()"):
clean_method = method.strip()
if clean_method.startswith("."):
clean_method = clean_method[1:]
if not clean_method.startswith("plot"):
clean_method = "plot." + clean_method
ax_or_fig = eval(f"self._obj.{clean_method}")
# get the plt figure
if hasattr(ax_or_fig, "get_figure"):
fig = ax_or_fig.get_figure()
elif hasattr(ax_or_fig, "figure"):
fig = ax_or_fig.figure
else:
fig = plt.gcf()
# convert to image PNG base64
buffer = io.BytesIO()
fig.savefig(buffer, format="png", bbox_inches="tight")
plt.close(fig)
img_b64 = base64.b64encode(buffer.getvalue()).decode("utf-8")
# send to HTML then to window Winform
html_df = self._get_base_html()
html_to_add = f'<img align="left" src="data:image/png;base64,{img_b64}">'
updated_html = re.sub(
r"</body>",
html_to_add + r"<br clear=left><hr><br></body>",
html_df,
flags=re.I,
)
viewerForm.HtmlText = updated_html
viewerForm.htmlDf = updated_html
def find_viewer():
for frm in System.Windows.Forms.Application.OpenForms:
if not frm.IsDisposed and (frm.Name == "PandasViewerForm" or frm.Text == "PandasViewer"):
ctrls = frm.Controls.Find("webBrowser1", True)
if ctrls.Length > 0 and not ctrls[0].IsDisposed:
return frm, ctrls[0]
return None, None
viewerForm, webBrowser1 = find_viewer()
if viewerForm is None:
viewerForm = MyForm()
webBrowser1 = viewerForm.Controls.Find("webBrowser1", True)[0]
viewerForm.Show()
else:
viewerForm.Name = "PandasViewerForm"
viewerForm.Tag = MyForm.init_htmlDf
viewerForm.DocumentText = MyForm.init_htmlDf
if not viewerForm.Visible:
viewerForm.Show()
if viewerForm.WindowState == FormWindowState.Minimized:
viewerForm.WindowState = FormWindowState.Normal
viewerForm.BringToFront()
viewerForm.Activate()
all_cable_tray = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_CableTray).WhereElementIsNotElementType().ToElements()
df = pd.DataFrame({'DB_Element': all_cable_tray})
#
df['Name'] = df.apply(lambda x: x['DB_Element'].get_Name(), axis=1)
#
df['Service'] = df.apply(lambda x: x['DB_Element'].get_Parameter(BuiltInParameter.RBS_CTC_SERVICE_TYPE).AsValueString(), axis=1).astype(str)
#
df['Hauteur'] = df.apply(lambda x: x['DB_Element'].get_Parameter(BuiltInParameter.RBS_CABLETRAY_HEIGHT_PARAM).AsValueString(), axis=1).astype(str)
#
df['Largeur'] = df.apply(lambda x: x['DB_Element'].get_Parameter(BuiltInParameter.RBS_CABLETRAY_WIDTH_PARAM).AsValueString(), axis=1).astype(str)
#
df['Longueur'] = df.apply(lambda x: x['DB_Element'].get_Parameter(BuiltInParameter.CURVE_ELEM_LENGTH).AsValueString(), axis=1).apply(pd.to_numeric)
#
df.viewer.Show(label="cableTray")
# show only 5 first rows
df.viewer.Show(5)
df.dtypes.to_frame().viewer.Show()
# describe
dfdes = df.describe()
# show describe
dfdes.viewer.Show(label="summary")
# show transpose dscribe
dfdes.T.viewer.Show()
##
# grouby and sum by 'Service'
dfg = df.drop(['DB_Element'], axis=1)
dfg = dfg.groupby('Service')['Longueur'].sum()
dfgroupService = dfg.to_frame()
dfgroupService.viewer.Show(label="by service")
# grouby and sum by 'Service' and 'Largeur'
dfg2 = df.drop(['DB_Element'], axis=1)
dfg2 = dfg2.groupby(['Service', 'Largeur']).sum()
dfg2.viewer.Show()
###
dfg.to_frame().viewer.Plot()
dfg.to_frame().viewer.Plot("plot.bar(rot=0)")
#
dfg.to_frame().viewer.Plot("plot.pie(subplots=True, figsize=(11, 6))")
df.viewer.Plot("box()")
# show a barplot
# transform the table pivot
dfg2_reset = dfg2.reset_index()
dfg2_reset.head(3).viewer.Show(label="dfg2_reset")
df_pivot = dfg2_reset.pivot_table(
index=['Service', 'Largeur'],
values=['Longueur'],
aggfunc='max', # ou 'max' selon votre besoin
)
# group name
df_pivot.columns.name = 'Dimension_Type'
# plot bar
df_pivot.viewer.Plot(
"plot(kind='bar', figsize=(20, 5.5), width=0.7, ylabel='Longueur (m)', xlabel='Service', rot=0)"
)
OUT = df_pivot.__repr__ , df_pivot.dtypes
import clr
import sys
import System
import uuid
clr.AddReference("System.Numerics")
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
import Autodesk.DesignScript.Geometry as DS
# import Revit API
clr.AddReference('RevitAPI')
import Autodesk
from Autodesk.Revit.DB import *
import Autodesk.Revit.DB as DB
# import specify namespace
from Autodesk.Revit.DB.Electrical import *
# import net library
from System import Array
from System.Collections.Generic import List, IList, Dictionary
# import transactionManager and DocumentManager
clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument
clr.AddReference('System.Drawing')
clr.AddReference('System.Windows.Forms')
import System.Drawing
import System.Windows.Forms
from System.Drawing import *
from System.Windows.Forms import *
import numpy as np
import pandas as pd
import webbrowser
import matplotlib.pyplot as plt
import io
import base64
import re
class MyForm(Form):
__namespace__ = "Dynamo.PandasViewer_" + uuid.uuid4().hex
# Modèle HTML mis à jour avec ajustement automatique des colonnes
init_htmlDf = """<!DOCTYPE html>
<html lang="fr">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta charset="utf-8" />
<title>PandasViewer</title>
<!-- CSS DataTables & Responsive -->
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.13.6/css/jquery.dataTables.min.css"/>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/responsive/2.5.0/css/responsive.dataTables.min.css"/>
<!-- jQuery & DataTables JS -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/responsive/2.5.0/js/dataTables.responsive.min.js"></script>
<style>
html, body {
font-family: Arial, sans-serif;
margin: 10px;
padding: 0;
background-color: #ffffff;
width: calc(100% - 20px);
}
table.dataTable {
background-color: #f0f2f5 !important;
width: 100% !important;
border-radius: 8px;
font-size: 12px;
table-layout: auto;
}
table.dataTable td {
word-wrap: break-word;
white-space: normal;
}
#error-banner {
display: none;
background-color: #ff4c4c;
color: white;
padding: 10px;
margin-bottom: 15px;
border-radius: 4px;
font-weight: bold;
}
</style>
</head>
<body>
<div id="error-banner">
Erreur : Les scripts DataTables n'ont pas pu se charger (blocage réseau ou sécurité).
</div>
<!-- Les tableaux Pandas s'insèrent ici -->
<script>
// Désactiver les fenêtres pop-up d'alerte DataTables
if (typeof $.fn.dataTable !== 'undefined') {
$.fn.dataTable.ext.errMode = 'none';
}
function initDataTables() {
if (typeof jQuery !== 'undefined') {
$('table').each(function() {
if (!$.fn.DataTable.isDataTable(this)) {
try {
$(this).addClass('display compact').DataTable({
"paging": true,
"searching": true,
"ordering": true,
"info": true,
"pageLength": 10,
"autoWidth": false, // EMPÊCHE LE CALCUL RIGIDE DES LARGEURS
"responsive": true, // ADAPTATION AUTOMATIQUE
"destroy": true,
"language": {
"url": "https://cdn.datatables.net/plug-ins/1.13.6/i18n/fr-FR.json"
}
});
} catch (err) {
console.log("Erreur d'initialisation DataTables :", err);
}
}
});
// Recalcul des colonnes après rendu complet
setTimeout(function() {
$.fn.dataTable.tables({ visible: true, api: true }).columns.adjust();
}, 200);
}
}
window.onload = function() {
if (typeof jQuery === 'undefined') {
document.getElementById('error-banner').style.display = 'block';
} else {
initDataTables();
}
};
// Recalculer la taille des colonnes lors du redimensionnement de la fenêtre
window.onresize = function() {
if (typeof jQuery !== 'undefined') {
$.fn.dataTable.tables({ visible: true, api: true }).columns.adjust();
}
};
</script>
</body>
</html>
"""
def __init__(self):
super().__init__()
self.htmlDf = MyForm.init_htmlDf
self.InitializeComponent()
def InitializeComponent(self):
self._webBrowser1 = System.Windows.Forms.WebBrowser()
self.SuspendLayout()
self._webBrowser1.Dock = System.Windows.Forms.DockStyle.Fill
self._webBrowser1.Location = System.Drawing.Point(0, 0)
self._webBrowser1.ScriptErrorsSuppressed = True
self._webBrowser1.Name = "webBrowser1"
self._webBrowser1.Size = System.Drawing.Size(800, 600)
self._webBrowser1.TabIndex = 0
self._webBrowser1.DocumentText = self.htmlDf
self._webBrowser1.DocumentCompleted += self.WebBrowser1DocumentCompleted
self.ClientSize = System.Drawing.Size(800, 600)
self.Controls.Add(self._webBrowser1)
self.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Show
self.Name = "Form27"
self.Text = "PandasViewer"
self.ResumeLayout(False)
def WebBrowser1DocumentCompleted(self, sender, e):
self._webBrowser1.Document.Body.ScrollIntoView(False)
def get_HtmlText(self):
return self.htmlDf
def set_HtmlText(self, value):
self.htmlDf = value
self._webBrowser1.Document.OpenNew(True)
self._webBrowser1.Document.Write(self.htmlDf)
self._webBrowser1.Refresh()
HtmlText = clr.clrproperty(str, get_HtmlText, set_HtmlText)
def ResetHtml(self):
self.htmlDf = self.init_htmlDf
# Extension Pandas
@pd.api.extensions.register_dataframe_accessor("viewer")
class HtmlAccessor:
def __init__(self, df_obj):
self._obj = df_obj
def _get_base_html(self):
html_content = getattr(viewerForm, "htmlDf", "")
if not html_content or "</body>" not in html_content:
return MyForm.init_htmlDf
return html_content
def Show(self, n=0, label=""):
html_df = self._get_base_html()
if isinstance(self._obj, pd.Series):
df_to_show = self._obj.to_frame()
else:
df_to_show = self._obj.copy()
if n > 0:
df_to_show = df_to_show.head(n)
# Nettoyage des MultiIndex pour préserver la structure DataTables
if isinstance(df_to_show.index, pd.MultiIndex):
df_to_show = df_to_show.reset_index()
if isinstance(df_to_show.columns, pd.MultiIndex):
df_to_show.columns = ['_'.join(map(str, col)).strip() for col in df_to_show.columns.values]
df_to_show = df_to_show.reset_index()
html_to_add = df_to_show.to_html(classes="display compact", escape=False)
if label:
html_to_add = f"<h3>{label}</h3>" + html_to_add
updated_html = re.sub(
r"(<script>\s*// Désactiver)", html_to_add + r"<br><hr><br>\1", html_df, flags=re.I
)
viewerForm.HtmlText = updated_html
viewerForm.htmlDf = updated_html
def Plot(self, method="plot()"):
clean_method = method.strip()
if clean_method.startswith("."):
clean_method = clean_method[1:]
if not clean_method.startswith("plot"):
clean_method = "plot." + clean_method
ax_or_fig = eval(f"self._obj.{clean_method}")
if hasattr(ax_or_fig, "get_figure"):
fig = ax_or_fig.get_figure()
elif hasattr(ax_or_fig, "figure"):
fig = ax_or_fig.figure
else:
fig = plt.gcf()
buffer = io.BytesIO()
fig.savefig(buffer, format="png", bbox_inches="tight")
plt.close(fig)
img_b64 = base64.b64encode(buffer.getvalue()).decode("utf-8")
html_df = self._get_base_html()
html_to_add = f'<img align="left" style="max-width:100%; height:auto;" src="data:image/png;base64,{img_b64}">'
updated_html = re.sub(
r"(<script>\s*// Désactiver)",
html_to_add + r"<br clear=left><hr><br>\1",
html_df,
flags=re.I,
)
viewerForm.HtmlText = updated_html
viewerForm.htmlDf = updated_html
def find_viewer():
for frm in System.Windows.Forms.Application.OpenForms:
if not frm.IsDisposed and (frm.Name == "PandasViewerForm" or frm.Text == "PandasViewer"):
ctrls = frm.Controls.Find("webBrowser1", True)
if ctrls.Length > 0 and not ctrls[0].IsDisposed:
return frm, ctrls[0]
return None, None
# Initialisation de la fenêtre
viewerForm, webBrowser1 = find_viewer()
if viewerForm is None:
viewerForm = MyForm()
webBrowser1 = viewerForm.Controls.Find("webBrowser1", True)[0]
viewerForm.Show()
else:
viewerForm.Name = "PandasViewerForm"
viewerForm.ResetHtml()
viewerForm.DocumentText = viewerForm.htmlDf
if not viewerForm.Visible:
viewerForm.Show()
if viewerForm.WindowState == FormWindowState.Minimized:
viewerForm.WindowState = FormWindowState.Normal
viewerForm.BringToFront()
viewerForm.Activate()
# ------- Traitement des données Revit ------- #
all_cable_tray = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_CableTray).WhereElementIsNotElementType().ToElements()
df = pd.DataFrame({'DB_Element': all_cable_tray})
df['Name'] = df.apply(lambda x: x['DB_Element'].get_Name(), axis=1)
df['Service'] = df.apply(lambda x: x['DB_Element'].get_Parameter(BuiltInParameter.RBS_CTC_SERVICE_TYPE).AsValueString(), axis=1).astype(str)
df['Hauteur'] = df.apply(lambda x: x['DB_Element'].get_Parameter(BuiltInParameter.RBS_CABLETRAY_HEIGHT_PARAM).AsValueString(), axis=1).astype(str)
df['Largeur'] = df.apply(lambda x: x['DB_Element'].get_Parameter(BuiltInParameter.RBS_CABLETRAY_WIDTH_PARAM).AsValueString(), axis=1).astype(str)
df['Longueur'] = df.apply(lambda x: x['DB_Element'].get_Parameter(BuiltInParameter.CURVE_ELEM_LENGTH).AsValueString(), axis=1).apply(pd.to_numeric)
# Affichage des tableaux
df.viewer.Show(label="cableTray")
df.viewer.Show(5, label="5 Premières Lignes")
dfdes = df.describe()
dfdes.viewer.Show(label="Summary")
dfg = df.drop(['DB_Element'], axis=1)
dfg = dfg.groupby('Service')['Longueur'].sum()
dfgroupService = dfg.to_frame()
dfgroupService.viewer.Show(label="By Service")
dfg2 = df.drop(['DB_Element'], axis=1)
dfg2 = dfg2.groupby(['Service', 'Largeur']).sum()
dfg2.viewer.Show(label="Grouped by Service and Largeur")
# Graphiques
dfg.to_frame().viewer.Plot("plot.bar(rot=0)")
dfg2_reset = dfg2.reset_index()
df_pivot = dfg2_reset.pivot_table(
index=['Service', 'Largeur'],
values=['Longueur'],
aggfunc='max',
)
df_pivot.columns.name = 'Dimension_Type'
df_pivot.viewer.Plot("plot(kind='bar', figsize=(20, 5.5), width=0.7, ylabel='Longueur (m)', xlabel='Service', rot=0)")
OUT = df_pivot.__repr__(), df_pivot.dtypes





0 commentaires:
Enregistrer un commentaire