Existe-t-il un moyen d'ajuster automatiquement les largeurs de colonne Excel avec pandas.ExcelWriter?


99

On me demande de générer des rapports Excel. J'utilise actuellement assez massivement des pandas pour mes données, alors j'aimerais naturellement utiliser la méthode pandas.ExcelWriter pour générer ces rapports. Cependant, les largeurs de colonne fixes posent un problème.

Le code que j'ai jusqu'à présent est assez simple. Disons que j'ai un dataframe appelé 'df':

writer = pd.ExcelWriter(excel_file_path, engine='openpyxl')
df.to_excel(writer, sheet_name="Summary")

Je regardais le code pandas, et je ne vois vraiment aucune option pour définir la largeur des colonnes. Existe-t-il une astuce dans l'univers pour faire en sorte que les colonnes s'adaptent automatiquement aux données? Ou puis-je faire quelque chose après coup dans le fichier xlsx pour ajuster les largeurs de colonne?

(J'utilise la bibliothèque OpenPyXL et génère des fichiers .xlsx - si cela fait une différence.)

Je vous remercie.


1
ne semble pas possible pour le moment, veuillez ouvrir un problème pour cette amélioration sur github (et peut-être un PR?). n'a pas l'air si difficile à faire.
Jeff le

merci Jeff, j'ai soumis le problème. je ne sais pas si j'aurai le temps de me plonger dans la base de code pandas pour le résoudre, mais on ne sait jamais :)
badideas

oui .... vu votre problème ..... commentez le problème si vous avez besoin d'aide! (essentiellement besoin de passer un argument facultatif à to_excel, peut-être col_style=dictqui contient des éléments de style d'en-tête col (plutôt que la valeur par défaut header_stylequi semble être codée en dur maintenant
Jeff

Réponses:


56

Inspiré par la réponse de user6178746 , j'ai ce qui suit:

# Given a dict of dataframes, for example:
# dfs = {'gadgets': df_gadgets, 'widgets': df_widgets}

writer = pd.ExcelWriter(filename, engine='xlsxwriter')
for sheetname, df in dfs.items():  # loop through `dict` of dataframes
    df.to_excel(writer, sheet_name=sheetname)  # send df to writer
    worksheet = writer.sheets[sheetname]  # pull worksheet object
    for idx, col in enumerate(df):  # loop through all columns
        series = df[col]
        max_len = max((
            series.astype(str).map(len).max(),  # len of largest item
            len(str(series.name))  # len of column name/header
            )) + 1  # adding a little extra space
        worksheet.set_column(idx, idx, max_len)  # set column width
writer.save()

7
FYI: Dans mon cas, je devais utiliser "index = False" dans l'appel "df.to_excel (...)", sinon les colonnes étaient
décalées

1
oui, j'ai aussi dû ajouter df.to_excel (écrivain, nom_feuille = nom de la feuille, index = False)
Heikki Pulkkinen

2
Si vous ne pouvez pas utiliser l' index = False (parce que vous avez un multiindice sur les lignes), vous pouvez obtenir la profondeur de niveau d'index avec df.index.nlevels puis l' utiliser pour ajouter à votre colonne set appel: worksheet.set_column(idx+nlevels, idx+nlevels, max_len). Sinon, la longueur est calculée pour la première colonne du cadre, puis appliquée à la première colonne dans Excel, qui est probablement l'index.
ac24

1
Pour tous ceux qui recherchent toujours cette réponse, cela enumerate(df)devrait être enumerate(df.columns)puisque vous parcourez chaque colonne dans df.
Dascienz

2
@Dascienz de la même manière que l'itération sur a dictitère réellement sur les clés dans le dict(vous n'avez pas à dire manuellement dict.keys()), l'itération sur a pd.DataFrameitère sur les colonnes. Vous n'avez pas à parcourir manuellement df.columns.
alichaudry

26

Je publie ceci parce que je viens de rencontrer le même problème et que j'ai constaté que la documentation officielle de Xlsxwriter et des pandas contient toujours cette fonctionnalité répertoriée comme non prise en charge. J'ai piraté ensemble une solution qui a résolu le problème que j'avais. En gros, je parcoure chaque colonne et j'utilise worksheet.set_column pour définir la largeur de la colonne == la longueur maximale du contenu de cette colonne.

Une note importante, cependant. Cette solution ne correspond pas aux en-têtes de colonne, simplement aux valeurs de colonne. Cela devrait être un changement facile si vous avez besoin d'ajuster les en-têtes à la place. J'espère que cela aide quelqu'un :)

import pandas as pd
import sqlalchemy as sa
import urllib


read_server = 'serverName'
read_database = 'databaseName'

read_params = urllib.quote_plus("DRIVER={SQL Server};SERVER="+read_server+";DATABASE="+read_database+";TRUSTED_CONNECTION=Yes")
read_engine = sa.create_engine("mssql+pyodbc:///?odbc_connect=%s" % read_params)

#Output some SQL Server data into a dataframe
my_sql_query = """ SELECT * FROM dbo.my_table """
my_dataframe = pd.read_sql_query(my_sql_query,con=read_engine)

#Set destination directory to save excel.
xlsFilepath = r'H:\my_project' + "\\" + 'my_file_name.xlsx'
writer = pd.ExcelWriter(xlsFilepath, engine='xlsxwriter')

#Write excel to file using pandas to_excel
my_dataframe.to_excel(writer, startrow = 1, sheet_name='Sheet1', index=False)

#Indicate workbook and worksheet for formatting
workbook = writer.book
worksheet = writer.sheets['Sheet1']

#Iterate through each column and set the width == the max length in that column. A padding length of 2 is also added.
for i, col in enumerate(my_dataframe.columns):
    # find length of column i
    column_len = my_dataframe[col].astype(str).str.len().max()
    # Setting the length if the column header is larger
    # than the max column value length
    column_len = max(column_len, len(col)) + 2
    # set the column length
    worksheet.set_column(i, i, column_len)
writer.save()

1
Bonne solution. J'aime la façon dont vous avez utilisé des pandas au lieu d'un autre paquet.

Je pense que vous avez besoin de la ()fonction max à l'intérieur: `max (column_len (), len (col)) + 2`
Serdia

21

Il n'y a probablement pas de moyen automatique de le faire pour le moment, mais comme vous utilisez openpyxl, la ligne suivante (adaptée d'une autre réponse de l'utilisateur Bufke sur la façon de faire manuellement ) vous permet de spécifier une valeur saine (en largeurs de caractères):

writer.sheets['Summary'].column_dimensions['A'].width = 15

Le moteur par défaut utilisé par les pandas d'ExcelWriter a changé depuis 2013 en Xlsxwriter, qui ne contient pas d' column_dimensionsattribut. Si vous souhaitez continuer à utiliser openpyxl, spécifiez-le simplement lors de la création de l'écrivain en utilisantpd.ExcelWriter(excel_filename, engine='openpyxl')
ojdo

@Sunil: vérifiez les autres réponses en utilisant Xlsxwritercomme moteur pour voir comment spécifier la largeur de colonne avec le moteur par défaut d'aujourd'hui.
ojdo

21

Il y a un joli package que j'ai commencé à utiliser récemment appelé StyleFrame.

il obtient DataFrame et vous permet de le styliser très facilement ...

par défaut, la largeur des colonnes s'ajuste automatiquement.

par exemple:

from StyleFrame import StyleFrame
import pandas as pd

df = pd.DataFrame({'aaaaaaaaaaa': [1, 2, 3], 
                   'bbbbbbbbb': [1, 1, 1],
                   'ccccccccccc': [2, 3, 4]})
excel_writer = StyleFrame.ExcelWriter('example.xlsx')
sf = StyleFrame(df)
sf.to_excel(excel_writer=excel_writer, row_to_add_filters=0,
            columns_and_rows_to_freeze='B2')
excel_writer.save()

vous pouvez également modifier la largeur des colonnes:

sf.set_column_width(columns=['aaaaaaaaaaa', 'bbbbbbbbb'],
                    width=35.3)


METTRE À JOUR

Dans la version 1.4, l' best_fitargument a été ajouté à StyleFrame.to_excel. Consultez la documentation .


Le package StyleFrame peut être facile à utiliser, mais je ne vois pas comment "par défaut, la largeur des colonnes s'ajuste automatiquement". Lorsque j'exécute l'exemple de code que vous avez donné, toutes les colonnes ont la même largeur et les trois en-têtes sont enveloppés. Vos échantillons de données sont également mal choisis, car ils ont tous presque la même largeur naturellement. Pour vraiment illustrer l'ajustement automatique, vous devez choisir des données très larges et des données étroites. Lorsque je fais cela pour moi-même, les largeurs de colonne sont toujours exactement les mêmes qu'avant. Il n'y a eu aucun ajustement.
John Y

Peut-être qu'à un moment donné de l'histoire de StyleFrame, les largeurs de colonne ont été automatiquement ajustées par défaut, mais au moins aujourd'hui, vous devez spécifier la ou les colonnes que vous souhaitez ajuster dans le best_fitparamètre. De plus, lorsque j'ai essayé cela, j'ai obtenu de très mauvais résultats .
John Y

la largeur semble être sur 1 colonne. J'ai essayé d'activer et de désactiver le indexparamètre mais pas de dés.

1
Merci! pour ceux qui recherchent: Comment vous ajoutez plus de style à l'en-tête par exemple: sf.apply_headers_style(Styler(bold=False))il m'a fallu beaucoup de temps pour comprendre cela. Et dans la déclaration d'importation, from StyleFrame import StyleFrame, Styler. voici toutes les options en dehors du gras: styleframe.readthedocs.io/en/2.0.5/…
Nikhil VJ

Malheureusement, cette réponse est obsolète et je n'obtiens des erreurs d'importation que si j'essaye de l'appliquer car l'API semble avoir changé de manière significative.
Hagbard le

10

En utilisant pandas et xlsxwriter, vous pouvez faire votre tâche, le code ci-dessous fonctionnera parfaitement dans Python 3.x. Pour plus de détails sur l'utilisation de XlsxWriter avec les pandas, ce lien peut être utile https://xlsxwriter.readthedocs.io/working_with_pandas.html

import pandas as pd
writer = pd.ExcelWriter(excel_file_path, engine='xlsxwriter')
df.to_excel(writer, sheet_name="Summary")
workbook = writer.book
worksheet = writer.sheets["Summary"]
#set the column width as per your requirement
worksheet.set_column('A:A', 25)
writer.save()

4

J'ai trouvé qu'il était plus utile d'ajuster la colonne en fonction de l'en-tête de colonne plutôt que du contenu de la colonne.

En utilisant df.columns.values.tolist()je génère une liste des en-têtes de colonne et utilise les longueurs de ces en-têtes pour déterminer la largeur des colonnes.

Voir le code complet ci-dessous:

import pandas as pd
import xlsxwriter

writer = pd.ExcelWriter(filename, engine='xlsxwriter')
df.to_excel(writer, index=False, sheet_name=sheetname)

workbook = writer.book # Access the workbook
worksheet= writer.sheets[sheetname] # Access the Worksheet

header_list = df.columns.values.tolist() # Generate list of headers
for i in range(0, len(header_list)):
    worksheet.set_column(i, i, len(header_list[i])) # Set column widths based on len(header)

writer.save() # Save the excel file

4

Au travail, j'écris toujours les dataframes dans des fichiers Excel. Donc, au lieu d'écrire le même code encore et encore, j'ai créé un module. Maintenant, je viens de l'importer et de l'utiliser pour écrire et formater les fichiers Excel. Il y a cependant un inconvénient, cela prend beaucoup de temps si la trame de données est très grande. Voici donc le code:

def result_to_excel(output_name, dataframes_list, sheet_names_list, output_dir):
    out_path = os.path.join(output_dir, output_name)
    writerReport = pd.ExcelWriter(out_path, engine='xlsxwriter',
                    datetime_format='yyyymmdd', date_format='yyyymmdd')
    workbook = writerReport.book
    # loop through the list of dataframes to save every dataframe into a new sheet in the excel file
    for i, dataframe in enumerate(dataframes_list):
        sheet_name = sheet_names_list[i]  # choose the sheet name from sheet_names_list
        dataframe.to_excel(writerReport, sheet_name=sheet_name, index=False, startrow=0)
        # Add a header format.
        format = workbook.add_format({
            'bold': True,
            'border': 1,
            'fg_color': '#0000FF',
            'font_color': 'white'})
        # Write the column headers with the defined format.
        worksheet = writerReport.sheets[sheet_name]
        for col_num, col_name in enumerate(dataframe.columns.values):
            worksheet.write(0, col_num, col_name, format)
        worksheet.autofilter(0, 0, 0, len(dataframe.columns) - 1)
        worksheet.freeze_panes(1, 0)
        # loop through the columns in the dataframe to get the width of the column
        for j, col in enumerate(dataframe.columns):
            max_width = max([len(str(s)) for s in dataframe[col].values] + [len(col) + 2])
            # define a max width to not get to wide column
            if max_width > 50:
                max_width = 50
            worksheet.set_column(j, j, max_width)
    writerReport.save()
    writerReport.close()
    return output_dir + output_name

J'ai eu l'erreur suivante lorsque j'ai répliqué ce code: AttributeError: l'objet 'str' n'a pas d'attribut 'to_excel'. Il pense que cela a quelque chose à voir avec la façon dont "dataframe_list" est créé. La mienne est une liste avec 6 noms de
dataframe

Oui, la "dataframe_list" doit avoir des dataframes et non des noms de dataframe.
rafat.ch

4

Ajuster dynamiquement toutes les longueurs de colonne

writer = pd.ExcelWriter('/path/to/output/file.xlsx') 
df.to_excel(writer, sheet_name='sheetName', index=False, na_rep='NaN')

for column in df:
    column_length = max(df[column].astype(str).map(len).max(), len(column))
    col_idx = df.columns.get_loc(column)
    writer.sheets['sheetName'].set_column(col_idx, col_idx, column_length)

Ajuster manuellement une colonne à l'aide du nom de la colonne

col_idx = df.columns.get_loc('columnName')
writer.sheets['sheetName'].set_column(col_idx, col_idx, 15)

Ajuster manuellement une colonne à l'aide de l'index de colonne

writer.sheets['sheetName'].set_column(col_idx, col_idx, 15)

Au cas où l'un des éléments ci-dessus échouerait avec

AttributeError: 'Worksheet' object has no attribute 'set_column'

assurez-vous d'installer xlsxwriter:

pip install xlsxwriter

2

Combinaison des autres réponses et commentaires et prise en charge des multi-indices:

def autosize_excel_columns(worksheet, df):
  autosize_excel_columns_df(worksheet, df.index.to_frame())
  autosize_excel_columns_df(worksheet, df, offset=df.index.nlevels)

def autosize_excel_columns_df(worksheet, df, offset=0):
  for idx, col in enumerate(df):
    series = df[col]
    max_len = max((
      series.astype(str).map(len).max(),
      len(str(series.name))
    )) + 1
    worksheet.set_column(idx+offset, idx+offset, max_len)

sheetname=...
df.to_excel(writer, sheet_name=sheetname, freeze_panes=(df.columns.nlevels, df.index.nlevels))
worksheet = writer.sheets[sheetname]
autosize_excel_columns(worksheet, df)
writer.save()

2
import re
import openpyxl
..
for col in _ws.columns:
    max_lenght = 0
    print(col[0])
    col_name = re.findall('\w\d', str(col[0]))
    col_name = col_name[0]
    col_name = re.findall('\w', str(col_name))[0]
    print(col_name)
    for cell in col:
        try:
            if len(str(cell.value)) > max_lenght:
                max_lenght = len(cell.value)
        except:
            pass
    adjusted_width = (max_lenght+2)
    _ws.column_dimensions[col_name].width = adjusted_width

1

La solution la plus simple consiste à spécifier la largeur de la colonne dans la méthode set_column.

    for worksheet in writer.sheets.values():
        worksheet.set_column(0,last_column_value, required_width_constant)

0
def auto_width_columns(df, sheetname):
    workbook = writer.book  
    worksheet= writer.sheets[sheetname] 

    for i, col in enumerate(df.columns):
        column_len = max(df[col].astype(str).str.len().max(), len(col) + 2)
        worksheet.set_column(i, i, column_len)

1
codes seulement ne répond pas à la question que vous devez ajouter quelques explications ou prendre le temps de lire la documentation sur Comment écrire une bonne réponse?
Gad le

1
salut! Bien que ce code puisse résoudre la question, inclure une explication sur comment et pourquoi cela résout le problème aiderait vraiment à améliorer la qualité de votre publication et entraînerait probablement plus de votes à la hausse. N'oubliez pas que vous répondez à la question des lecteurs à l'avenir, pas seulement à la personne qui la pose maintenant. Veuillez modifier votre réponse pour ajouter des explications et donner une indication des limites et des hypothèses applicables.
Brian le
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.