Gibt es eine Möglichkeit, die Breite von Excel-Spalten mit pandas.ExcelWriter automatisch anzupassen?

98

Ich werde gebeten, einige Excel-Berichte zu erstellen. Ich verwende derzeit ziemlich häufig Pandas für meine Daten, daher möchte ich natürlich die pandas.ExcelWriter-Methode verwenden, um diese Berichte zu generieren. Die festen Spaltenbreiten sind jedoch ein Problem.

Der Code, den ich bisher habe, ist einfach genug. Angenommen, ich habe einen Datenrahmen mit dem Namen 'df':

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

Ich habe den Pandas-Code durchgesehen und sehe keine Optionen zum Festlegen der Spaltenbreite. Gibt es da draußen im Universum einen Trick, um die Spalten automatisch an die Daten anzupassen? Oder kann ich nachträglich etwas an der xlsx-Datei tun, um die Spaltenbreiten anzupassen?

(Ich verwende die OpenPyXL-Bibliothek und generiere XLSX-Dateien - wenn das einen Unterschied macht.)

Danke dir.

badideas
quelle
1
sieht momentan nicht möglich aus, bitte öffnen Sie ein Problem für diese Verbesserung auf Github (und vielleicht eine PR?). sieht nicht so schwer aus.
Jeff
danke Jeff, ich habe das Problem eingereicht. Ich bin mir nicht sicher, ob ich Zeit habe, tatsächlich in die Pandas-Codebasis
einzutauchen
yep .... habe dein Problem gesehen ..... kommentiere das Problem, wenn du Hilfe brauchst! (Im Wesentlichen muss ein optionales Argument an übergeben werden to_excel, das möglicherweise col_style=dictElemente im Col-Header-Stil enthält (anstelle der Standardeinstellung, header_styledie derzeit fest codiert zu sein scheint
Jeff,

Antworten:

55

Inspiriert von der Antwort von user6178746 habe ich Folgendes:

# 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()
Alichaudry
quelle
7
Zu Ihrer Information: In meinem Fall musste ich „index = False“ in der „df.to_excel (...)“ Anruf oder auch die Spalten verwenden , waren weg von 1
denvar
1
Ja, ich musste auch df.to_excel hinzufügen (Schriftsteller, Blattname = Blattname, Index = Falsch)
Heikki Pulkkinen
2
Wenn Sie index = False nicht verwenden können (weil Sie einen Multiindex für Zeilen haben), können Sie die Tiefe der Indexebene mit df.index.nlevels abrufen und diese dann verwenden, um Ihren festgelegten Spaltenaufruf zu ergänzen : worksheet.set_column(idx+nlevels, idx+nlevels, max_len). Andernfalls wird die Länge für die erste Spalte des Frames berechnet und dann auf die erste Spalte im Excel angewendet , bei der es sich wahrscheinlich um den Index handelt.
AC24
1
Für alle, die noch nach dieser Antwort suchen, enumerate(df)sollte es sein , dass enumerate(df.columns)Sie über jede Spalte in iterieren df.
Dascienz
2
@Dascienz auf die gleiche Weise, wie das Iterieren über eine dicttatsächlich über die Schlüssel in der dict(Sie müssen nicht manuell sagen dict.keys()) iteriert, iteriert über eine pd.DataFrameIteration über die Spalten. Sie müssen nicht manuell durchlaufen df.columns.
Alichaudry
26

Ich poste dies, weil ich gerade auf dasselbe Problem gestoßen bin und festgestellt habe, dass in der offiziellen Dokumentation für Xlsxwriter und Pandas diese Funktionalität weiterhin als nicht unterstützt aufgeführt ist. Ich habe eine Lösung gehackt, die das Problem gelöst hat, das ich hatte. Ich iteriere einfach durch jede Spalte und benutze worksheet.set_column, um die Spaltenbreite == die maximale Länge des Inhalts dieser Spalte festzulegen.

Ein wichtiger Hinweis jedoch. Diese Lösung passt nicht zu den Spaltenüberschriften, sondern nur zu den Spaltenwerten. Dies sollte jedoch eine einfache Änderung sein, wenn Sie stattdessen die Header anpassen müssen. Hoffe das hilft jemandem :)

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()
TrigonaMinima
quelle
1
Gute Lösung. Mir gefällt, wie Sie Pandas anstelle eines anderen Pakets verwendet haben.
Ich denke, Sie brauchen ()innerhalb der Max-Funktion: `max (column_len (), len (col)) + 2`
Serdia
21

Derzeit gibt es wahrscheinlich keine automatische Möglichkeit, dies zu tun. Wenn Sie jedoch openpyxl verwenden, können Sie in der folgenden Zeile (angepasst an eine andere Antwort von Benutzer Bufke zur manuellen Vorgehensweise ) einen vernünftigen Wert (in Zeichenbreiten) angeben:

writer.sheets['Summary'].column_dimensions['A'].width = 15
ojdo
quelle
Die Standard-ExcelWriter-Engine, die Pandas verwenden, wurde seit 2013 in Xlsxwriter geändert, das kein column_dimensionsAttribut enthält. Wenn Sie weiterhin openpyxl verwenden möchten, geben Sie es einfach an, wenn Sie den Writer mitpd.ExcelWriter(excel_filename, engine='openpyxl')
ojdo
@Sunil: Überprüfen Sie die anderen Antworten, die Xlsxwriterals Engine verwendet werden, um festzustellen , wie die Spaltenbreite mit der heutigen Standard-Engine angegeben wird.
ojdo
21

Es gibt ein schönes Paket, das ich kürzlich verwendet habe und das StyleFrame heißt.

Es erhält DataFrame und ermöglicht es Ihnen, es sehr einfach zu gestalten ...

Standardmäßig wird die Spaltenbreite automatisch angepasst.

beispielsweise:

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()

Sie können auch die Spaltenbreite ändern:

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


AKTUALISIEREN

In Version 1.4 wurde das best_fitArgument hinzugefügt StyleFrame.to_excel. Siehe die Dokumentation .

AsafSH
quelle
Das StyleFrame-Paket ist zwar einfach zu verwenden, aber ich sehe nicht, wie "die Spaltenbreite standardmäßig automatisch angepasst wird". Wenn ich das von Ihnen angegebene Codebeispiel ausführe, haben alle Spalten die gleiche Breite und alle drei Überschriften werden umbrochen. Ihre Beispieldaten sind ebenfalls schlecht ausgewählt, da sie natürlich alle fast gleich breit sind. Um die automatische Anpassung wirklich zu veranschaulichen, sollten Sie einige wirklich breite Daten und einige enge Daten auswählen. Wenn ich das für mich selbst mache, sind die Spaltenbreiten immer noch genau die gleichen wie zuvor. Es gab keinerlei Anpassung.
John Y
Möglicherweise wurden zu einem bestimmten Zeitpunkt im Verlauf von StyleFrame die Spaltenbreiten standardmäßig automatisch angepasst, aber zumindest heute müssen Sie die Spalte oder Spalten angeben, die im best_fitParameter angepasst werden sollen. Als ich das versuchte, bekam ich auch sehr schlechte Ergebnisse .
John Y
Die Breite scheint außerhalb einer Spalte zu liegen. Ich habe versucht, den indexParameter zu aktivieren und zu deaktivieren , aber keine Würfel.
1
Vielen Dank! für diejenigen, die suchen: Wie Sie dem Header mehr Styling hinzufügen, zum Beispiel: sf.apply_headers_style(Styler(bold=False))Ich habe lange gebraucht, um das herauszufinden. Und in der import-Anweisung , from StyleFrame import StyleFrame, Styler. Hier sind alle Optionen außer fett: styleframe.readthedocs.io/en/2.0.5/…
Nikhil VJ
Leider ist diese Antwort veraltet und ich erhalte nur dann Importfehler, wenn ich versuche, sie anzuwenden, da sich die API anscheinend erheblich geändert hat.
Hagbard
10

Mit Pandas und xlsxwriter können Sie Ihre Aufgabe erledigen. Der folgende Code funktioniert perfekt in Python 3.x. Weitere Informationen zum Arbeiten mit XlsxWriter mit Pandas finden Sie unter 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()
Ashu007
quelle
4

Bei der Arbeit schreibe ich immer die Datenrahmen, um Dateien zu übertreffen. Anstatt immer wieder denselben Code zu schreiben, habe ich einen Modul erstellt. Jetzt importiere ich es einfach und benutze es, um die Excel-Dateien zu schreiben und zu formatieren. Es gibt jedoch einen Nachteil: Es dauert lange, wenn der Datenrahmen besonders groß ist. Also hier ist der 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
rafat.ch
quelle
Beim Replizieren dieses Codes wurde folgende Fehlermeldung angezeigt: AttributeError: Das Objekt 'str' hat kein Attribut 'to_excel'. Es hat etwas mit der Art und Weise zu tun, wie "dataframe_list" erstellt wird. Meins ist eine Liste mit 6 Datenrahmennamen
user3019973
3

Ich fand, dass es nützlicher war, die Spalte basierend auf der Spaltenüberschrift anzupassen, als den Spalteninhalt.

Mit df.columns.values.tolist()I generiere ich eine Liste der Spaltenüberschriften und benutze die Länge dieser Überschriften, um die Breite der Spalten zu bestimmen.

Siehe den vollständigen Code unten:

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
jack1536
quelle
3

Passen Sie alle Spaltenlängen dynamisch an

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)

Passen Sie eine Spalte manuell mit dem Spaltennamen an

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

Passen Sie eine Spalte mithilfe des Spaltenindex manuell an

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

Falls einer der oben genannten Fehler auftritt

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

Stellen Sie sicher, dass Sie Folgendes installieren xlsxwriter:

pip install xlsxwriter
Giorgos Myrianthous
quelle
@ Karu Schöner Fang. Ich habe meine Antwort aktualisiert.
Giorgos Myrianthous
2

Kombinieren Sie die anderen Antworten und Kommentare und unterstützen Sie auch Multi-Indizes:

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()
kgibm
quelle
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
Ssubrat Rrudra
quelle
1

Die einfachste Lösung besteht darin, die Spaltenbreite in der Methode set_column anzugeben.

    for worksheet in writer.sheets.values():
        worksheet.set_column(0,last_column_value, required_width_constant)
Ashish Jith
quelle
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)
Michel Kluger
quelle
1
Codes beantworten nur nicht die Frage, die Sie haben, um einige Erklärungen hinzuzufügen oder sich Zeit zu nehmen und die Dokumentation zu lesen. Wie schreibe ich eine gute Antwort?
Gad
1
Hallo! Während dieser Code die Frage lösen kann, einschließlich einer Erklärung, wie und warum dies das Problem löst, würde dies wirklich dazu beitragen, die Qualität Ihres Beitrags zu verbessern, und wahrscheinlich zu mehr Up-Votes führen. Denken Sie daran, dass Sie in Zukunft die Frage für die Leser beantworten, nicht nur für die Person, die jetzt fragt. Bitte bearbeiten Sie Ihre Antwort, um Erklärungen hinzuzufügen und anzugeben, welche Einschränkungen und Annahmen gelten.
Brian