Existe uma maneira de ajustar automaticamente as larguras das colunas do Excel com o pandas.ExcelWriter?

99

Estou sendo solicitado a gerar alguns relatórios do Excel. No momento, estou usando bastante o pandas para meus dados, então, naturalmente, gostaria de usar o método pandas.ExcelWriter para gerar esses relatórios. No entanto, as larguras fixas das colunas são um problema.

O código que tenho até agora é bastante simples. Digamos que eu tenha um dataframe chamado 'df':

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

Eu estava examinando o código do pandas e não vejo nenhuma opção para definir as larguras das colunas. Existe algum truque no universo para fazer com que as colunas se ajustem automaticamente aos dados? Ou há algo que posso fazer após o fato no arquivo xlsx para ajustar as larguras das colunas?

(Estou usando a biblioteca OpenPyXL e gerando arquivos .xlsx - se isso fizer alguma diferença.)

Obrigado.

badideas
fonte
1
não parece possível no momento, abra uma edição para esta melhoria no github (e talvez um PR?). não parece tão difícil de fazer.
Jeff
obrigado Jeff, enviei o problema. Não tenho certeza se terei tempo de mergulhar na base de código dos pandas para resolvê-lo, mas nunca se sabe :)
badideas
Sim ... vi seu problema ... comente sobre o problema se precisar de ajuda! (essencialmente precisa passar um argumento opcional para to_excel, talvez col_style=dictque contenha elementos de estilo de cabeçalho col (em vez do padrão header_styleque parece estar codificado agora
Jeff

Respostas:

56

Inspirado pela resposta do usuário6178746 , tenho o seguinte:

# 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
fonte
7
Para sua informação: no meu caso, eu precisava usar "index = False" na chamada "df.to_excel (...)", ou as colunas estavam desativadas em 1
denvar
1
Sim, também tive que adicionar df.to_excel (escritor, nome_da_pasta = nome da folha, índice = Falso)
Heikki Pulkkinen
2
Se você não pode usar o índice = False (porque você tem um multiindex em linhas), então você pode obter a profundidade do nível de índice com df.index.nlevels e depois usar isso para adicionar a sua chamada coluna set: worksheet.set_column(idx+nlevels, idx+nlevels, max_len). Caso contrário, o comprimento é calculado para a primeira coluna do quadro e, em seguida, aplicado à primeira coluna do Excel, que provavelmente é o índice.
ac24 de
1
Para quem ainda está procurando por essa resposta, enumerate(df)deve ser, enumerate(df.columns)já que você está iterando cada coluna em df.
Dascienz
2
@Dascienz da mesma maneira que iterar sobre a dictrealmente itera sobre as chaves no dict(você não precisa dizer manualmente dict.keys()), iterar sobre a pd.DataFrameitera sobre as colunas. Você não precisa iterar manualmente df.columns.
alichaudry
26

Estou postando isso porque acabei de ter o mesmo problema e descobri que a documentação oficial do Xlsxwriter e do pandas ainda tem essa funcionalidade listada como sem suporte. Eu criei uma solução que resolveu o problema que eu estava tendo. Basicamente, eu apenas itero por cada coluna e uso worksheet.set_column para definir a largura da coluna == o comprimento máximo do conteúdo dessa coluna.

Uma nota importante, entretanto. Esta solução não cabe nos cabeçalhos das colunas, apenas nos valores das colunas. Essa deve ser uma mudança fácil se você precisar ajustar os cabeçalhos. Espero que isso ajude alguém :)

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
fonte
1
Boa solução. Gosto de como você usou pandas em vez de outro pacote.
Acho que você precisa da ()função max: `max (column_len (), len (col)) + 2`
Serdia
21

Provavelmente não há uma maneira automática de fazer isso agora, mas como você usa openpyxl, a seguinte linha (adaptada de outra resposta do usuário Bufke sobre como fazer manualmente ) permite que você especifique um valor lógico (em larguras de caracteres):

writer.sheets['Summary'].column_dimensions['A'].width = 15
ojdo
fonte
O mecanismo padrão do ExcelWriter que o pandas está usando mudou desde 2013 para Xlsxwriter, que não contém um column_dimensionsatributo. Se você quiser continuar usando o openpyxl, simplesmente especifique-o ao criar o gravador usandopd.ExcelWriter(excel_filename, engine='openpyxl')
ojdo
@ Sunil: verifique as outras respostas usando Xlsxwritercomo mecanismo para ver como especificar a largura da coluna com o mecanismo padrão de hoje.
ojdo
21

Há um bom pacote que comecei a usar recentemente chamado StyleFrame.

obtém DataFrame e permite estilizá-lo com muita facilidade ...

por padrão, a largura das colunas é ajustada automaticamente.

por exemplo:

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

você também pode alterar a largura das colunas:

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


ATUALIZAR

Na versão 1.4, o best_fitargumento foi adicionado StyleFrame.to_excel. Veja a documentação .

AsafSH
fonte
O pacote StyleFrame pode ser fácil de usar, mas não vejo como "por padrão, a largura das colunas é ajustada automaticamente". Quando executo o exemplo de código que você forneceu, todas as colunas têm a mesma largura e todos os três cabeçalhos são agrupados. Seus dados de amostra também são mal escolhidos, porque eles têm quase a mesma largura naturalmente. Para realmente ilustrar o ajuste automático, você deve escolher alguns dados realmente amplos e alguns dados estreitos. Quando eu faço isso sozinho, as larguras das colunas ainda são exatamente as mesmas de antes. Não houve nenhum ajuste.
John Y
Talvez em um ponto da história do StyleFrame, as larguras das colunas foram ajustadas automaticamente por padrão, mas pelo menos hoje, você tem que especificar a coluna ou colunas que deseja ajustar no best_fitparâmetro. Além disso, quando tentei fazer isso, obtive resultados muito ruins .
John Y
a largura parece estar fora de 1 coluna. Tentei habilitar e desabilitar o indexparâmetro mas nenhum dado.
1
obrigado! para quem procura: Como você adiciona mais estilo ao cabeçalho, por exemplo: sf.apply_headers_style(Styler(bold=False))demorei muito para descobrir isso. E na declaração de importação from StyleFrame import StyleFrame, Styler,. aqui estão todas as opções, exceto negrito: styleframe.readthedocs.io/en/2.0.5/…
Nikhil VJ
Infelizmente, esta resposta está desatualizada e eu só recebo erros de importação se tentar aplicá-la, pois a API parece ter mudado significativamente.
Hagbard
10

Usando pandas e xlsxwriter você pode fazer sua tarefa, o código abaixo funcionará perfeitamente em Python 3.x. Para obter mais detalhes sobre como trabalhar com XlsxWriter com pandas, este link pode 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
fonte
4

Descobri que era mais útil ajustar a coluna com base no cabeçalho da coluna do que no conteúdo da coluna.

Usando, df.columns.values.tolist()eu gero uma lista dos cabeçalhos das colunas e uso os comprimentos desses cabeçalhos para determinar a largura das colunas.

Veja o código completo abaixo:

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

No trabalho, estou sempre gravando os dataframes em arquivos do Excel. Portanto, em vez de escrever o mesmo código repetidamente, criei um módulo. Agora, acabei de importá-lo e usá-lo para escrever e formatar os arquivos do Excel. Há uma desvantagem, porém, leva muito tempo se o dataframe for muito grande. Então, aqui está o 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()
    writerReport.close()
    return output_dir + output_name
rafat.ch
fonte
Recebi o seguinte erro quando repliquei este código: AttributeError: 'str' objeto não tem atributo 'to_excel'. Ele acha que tem algo a ver com a forma como "dataframe_list" é criada. A minha é uma lista com 6 nomes de
dataframe
Sim, o "dataframe_list" deve ter dataframes e não nomes de dataframe.
rafat.ch
4

Ajuste dinamicamente todos os comprimentos das colunas

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)

Ajuste manualmente uma coluna usando o nome da coluna

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

Ajuste manualmente uma coluna usando o Índice de coluna

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

Caso alguma das opções acima esteja falhando com

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

certifique-se de instalar xlsxwriter:

pip install xlsxwriter
Giorgos Myrianthous
fonte
2

Combinando as outras respostas e comentários e também suportando vários í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
fonte
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
fonte
1

A solução mais fácil é especificar a largura da coluna no método set_column.

    for worksheet in writer.sheets.values():
        worksheet.set_column(0,last_column_value, required_width_constant)
Ashish Jith
fonte
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
fonte
1
os códigos só não respondem à pergunta, você tem que adicionar algumas explicações ou dedicar algum tempo para ler a documentação sobre Como escrevo uma boa resposta?
Gad
1
Olá! Embora este código possa resolver a questão, incluir uma explicação de como e por que isso resolve o problema realmente ajudaria a melhorar a qualidade da sua postagem e provavelmente resultaria em mais votos positivos. Lembre-se de que você está respondendo às perguntas dos leitores no futuro, não apenas da pessoa que está perguntando agora. Por favor edite sua resposta para adicionar explicações e dar uma indicação do que limitações e premissas se aplicam.
Brian