¿Hay alguna manera de ajustar automáticamente el ancho de las columnas de Excel con pandas.ExcelWriter?

105

Se me pide que genere algunos informes de Excel. Actualmente estoy usando pandas bastante para mis datos, así que, naturalmente, me gustaría usar el método pandas.ExcelWriter para generar estos informes. Sin embargo, los anchos de columna fijos son un problema.

El código que tengo hasta ahora es bastante simple. Digamos que tengo un marco de datos llamado 'df':

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

Estaba mirando el código de pandas y realmente no veo ninguna opción para establecer el ancho de las columnas. ¿Existe algún truco en el universo para que las columnas se ajusten automáticamente a los datos? ¿O hay algo que pueda hacer después del hecho en el archivo xlsx para ajustar el ancho de las columnas?

(Estoy usando la biblioteca OpenPyXL y generando archivos .xlsx, si eso hace alguna diferencia).

Gracias.

malas ideas
fuente
1
no parece posible en este momento, abra un problema para esta mejora en github (¿y tal vez un PR?). no parece tan difícil de hacer.
Jeff
gracias Jeff, he enviado el problema. No estoy seguro de si tendré tiempo para sumergirme en el código base de pandas para resolverlo, pero nunca se sabe :)
badideas
sí .... vi su problema ..... comente sobre el problema si necesita ayuda! (esencialmente necesita pasar un argumento opcional a to_excel, tal vez col_style=dictque contenga elementos de estilo de encabezado col (en lugar del predeterminado header_styleque parece estar codificado ahora
Jeff

Respuestas:

59

Inspirado por la respuesta del usuario6178746 , tengo lo siguiente:

# 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
fuente
8
FYI: En mi caso, necesitaba usar "index = False" en la llamada "df.to_excel (...)", o de lo contrario las columnas estaban desactivadas por 1
denvar
1
sí, también tuve que agregar df.to_excel (escritor, sheet_name = sheetname, index = False)
Heikki Pulkkinen
2
Si no puede utilizar el índice = False (porque tiene un multiindex en filas), entonces se puede obtener la profundidad de nivel de índice con df.index.nlevels y luego usar esto para añadir a su establecimiento de llamada columna: worksheet.set_column(idx+nlevels, idx+nlevels, max_len). De lo contrario, la longitud se calcula para la primera columna del marco y luego se aplica a la primera columna en Excel, que probablemente sea el índice.
ac24
1
Para cualquiera que todavía busque esta respuesta, enumerate(df)debería serlo, enumerate(df.columns)ya que está iterando sobre cada columna en df.
Dascienz
2
@Dascienz de la misma manera iterando sobre a en dictrealidad itera sobre las claves en dict(no tiene que decirlo manualmente dict.keys()), iterando sobre a pd.DataFrameitera sobre las columnas. No tiene que iterar manualmente df.columns.
alichaudry
28

Estoy publicando esto porque acabo de encontrarme con el mismo problema y descubrí que la documentación oficial de Xlsxwriter y pandas todavía tiene esta funcionalidad listada como no compatible. Hackeé una solución que resolvió el problema que estaba teniendo. Básicamente, solo itero a través de cada columna y uso worksheet.set_column para establecer el ancho de la columna == la longitud máxima del contenido de esa columna.

Sin embargo, una nota importante. Esta solución no se ajusta a los encabezados de columna, simplemente a los valores de columna. Sin embargo, debería ser un cambio fácil si necesita ajustar los encabezados. Espero que esto ayude a alguien :)

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
fuente
1
Buena solución. Me gusta cómo usaste pandas en lugar de otro paquete.
Creo que necesitas ()dentro de la función máxima: `max (column_len (), len (col)) + 2`
Serdia
21

Probablemente no haya una forma automática de hacerlo en este momento, pero a medida que usa openpyxl, la siguiente línea (adaptada de otra respuesta del usuario Bufke sobre cómo hacerlo manualmente ) le permite especificar un valor sano (en anchos de caracteres):

writer.sheets['Summary'].column_dimensions['A'].width = 15
ojdo
fuente
El motor de ExcelWriter predeterminado que utilizan los pandas ha cambiado desde 2013 a Xlsxwriter, que no contiene ningún column_dimensionsatributo. Si desea seguir usando openpyxl, simplemente especifíquelo al crear el escritor usandopd.ExcelWriter(excel_filename, engine='openpyxl')
ojdo
@Sunil: verifique las otras respuestas usando Xlsxwritercomo motor para ver cómo especificar el ancho de columna con el motor predeterminado de hoy.
ojdo
21

Hay un buen paquete que comencé a usar recientemente llamado StyleFrame.

obtiene DataFrame y le permite diseñarlo muy fácilmente ...

de forma predeterminada, el ancho de las columnas se ajusta automáticamente.

por ejemplo:

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

también puede cambiar el ancho de las columnas:

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

ACTUALIZACIÓN 1

En la versión 1.4 best_fitse agregó el argumento StyleFrame.to_excel. Consulte la documentación .

ACTUALIZACIÓN 2

Aquí hay una muestra de código que funciona para StyleFrame 3.xx

from styleframe import StyleFrame
import pandas as pd

columns = ['aaaaaaaaaaa', 'bbbbbbbbb', 'ccccccccccc', ]
df = pd.DataFrame(data={
        'aaaaaaaaaaa': [1, 2, 3, ],
        'bbbbbbbbb': [1, 1, 1, ],
        'ccccccccccc': [2, 3, 4, ],
    }, columns=columns,
)
excel_writer = StyleFrame.ExcelWriter('example.xlsx')
sf = StyleFrame(df)
sf.to_excel(
    excel_writer=excel_writer, 
    best_fit=columns,
    columns_and_rows_to_freeze='B2', 
    row_to_add_filters=0,
)
excel_writer.save()
AsafSH
fuente
El paquete StyleFrame puede ser fácil de usar, pero no veo cómo "por defecto el ancho de las columnas se ajusta automáticamente". Cuando ejecuto la muestra de código que proporcionó, todas las columnas tienen el mismo ancho y los tres encabezados están ajustados. Sus datos de muestra también están mal elegidos, porque todos tienen casi el mismo ancho, naturalmente. Para ilustrar realmente el ajuste automático, debe elegir algunos datos realmente amplios y algunos datos estrechos. Cuando hago esto por mí mismo, los anchos de columna siguen siendo exactamente los mismos que antes. No hubo ajuste alguno.
John Y
Tal vez en un momento del historial de StyleFrame, los anchos de columna se ajustaron automáticamente de forma predeterminada, pero al menos hoy, debe especificar la columna o columnas que desea ajustar en el best_fitparámetro. Además, cuando probé esto, obtuve resultados muy pobres .
John Y
el ancho parece estar fuera de 1 columna. Intenté habilitar y deshabilitar el indexparámetro pero no dados.
1
¡Gracias! para aquellos que buscan: Cómo agregar más estilo al encabezado, por ejemplo: sf.apply_headers_style(Styler(bold=False))me tomó mucho tiempo darme cuenta de eso. Y en la declaración de importación, from StyleFrame import StyleFrame, Styler. aquí están todas las opciones además de negrita: styleframe.readthedocs.io/en/2.0.5/…
Nikhil VJ
1
@Hagbard a partir de la versión 3, la importación debe ser from styleframe import StyleFramepara cumplir con las convenciones de nombres
PEP8
11

Al usar pandas y xlsxwriter, puede hacer su tarea, el siguiente código funcionará perfectamente en Python 3.x. Para obtener más detalles sobre cómo trabajar con XlsxWriter con pandas, este enlace puede ser útil 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
fuente
5

Ajustar dinámicamente todas las longitudes de las columnas

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)

Ajustar manualmente una columna usando el nombre de la columna

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

Ajustar manualmente una columna usando el índice de columna

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

En caso de que alguno de los anteriores falle con

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

asegúrese de instalar xlsxwriter:

pip install xlsxwriter
Giorgos Myrianthous
fuente
4

Descubrí que era más útil ajustar la columna en función del encabezado de la columna en lugar del contenido de la columna.

Usando df.columns.values.tolist()genero una lista de los encabezados de columna y uso las longitudes de estos encabezados para determinar el ancho de las columnas.

Vea el código completo a continuación:

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
fuente
4

En el trabajo, siempre estoy escribiendo los marcos de datos en archivos de Excel. Entonces, en lugar de escribir el mismo código una y otra vez, he creado un módulo. Ahora solo lo importo y lo uso para escribir y formatear los archivos de Excel. Sin embargo, hay una desventaja: lleva mucho tiempo si el marco de datos es extra grande. Así que aquí está el código:

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()
    return output_dir + output_name

rafat.ch
fuente
Recibí el siguiente error cuando repliqué este código: AttributeError: el objeto 'str' no tiene atributo 'to_excel'. Piensa que tiene algo que ver con la forma en que se crea "dataframe_list". La mía es una lista con 6 nombres de marcos de datos
user3019973
Sí, "dataframe_list" debe tener marcos de datos y no nombres de marcos de datos.
rafat.ch
2

Combinando las otras respuestas y comentarios y también apoyando múltiples índices:

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
fuente
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
fuente
1

La solución más sencilla es especificar el ancho de la columna en el método set_column.

    for worksheet in writer.sheets.values():
        worksheet.set_column(0,last_column_value, required_width_constant)
Ashish Jith
fuente
1
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
fuente
1
Los códigos solamente no responden a la pregunta, tiene que agregar algunas explicaciones o tomarse un tiempo y leer la documentación sobre ¿Cómo escribo una buena respuesta?
Gad
1
¡Hola! Si bien este código puede resolver la pregunta, incluir una explicación de cómo y por qué esto resuelve el problema realmente ayudaría a mejorar la calidad de su publicación y probablemente resultaría en más votos a favor. Recuerde que está respondiendo la pregunta a los lectores en el futuro, no solo a la persona que pregunta ahora. Por favor, editar su respuesta para agregar explicaciones y dar una indicación de lo que se aplican limitaciones y supuestos.
Brian
0

Sí, hay algo que puede hacer después del hecho en el archivo xlsx para ajustar el ancho de las columnas. Utilice xlwings para ajustar automáticamente las columnas. Es una solución bastante simple, vea las seis últimas líneas del código de ejemplo. La ventaja de este procedimiento es que no tiene que preocuparse por el tamaño de fuente, el tipo de fuente o cualquier otra cosa. Requisito: instalación de Excel.

import pandas as pd
import xlwings as xw

report_file = "test.xlsx"

df1 = pd.DataFrame([
    ('this is a long term1', 1, 1, 3),
    ('this is a long term2', 1, 2, 5),
    ('this is a long term3', 1, 1, 6),
    ('this is a long term2', 1, 1, 9),
    ], columns=['term', 'aaaa', 'bbbbbbb', "cccccccccccccccccccccccccccccccccccccccccccccc"])

writer = pd.ExcelWriter(report_file, engine="xlsxwriter")
df1.to_excel(writer, sheet_name="Sheet1", index=False)

workbook = writer.book
worksheet1 = writer.sheets["Sheet1"]
num_format = workbook.add_format({"num_format": '#,##0.00'})

worksheet1.set_column("B:D", cell_format=num_format)
writer.save()

# Autofit all columns with xlwings.
app = xw.App(visible=False)
wb = xw.Book(report_file)

for ws in wb.sheets:
    ws.autofit(axis="columns")

wb.save(report_file)
app.quit()
mouwsy
fuente
Funciona solo en Windows y MacOS, aunque no en Linux
Guido hace