Есть ли способ автоматически настроить ширину столбцов Excel с помощью pandas.ExcelWriter?


105

Меня просят создать несколько отчетов в формате Excel. В настоящее время я довольно активно использую pandas для своих данных, поэтому, естественно, я хотел бы использовать метод pandas.ExcelWriter для создания этих отчетов. Однако фиксированная ширина столбца является проблемой.

Код, который у меня есть до сих пор, достаточно прост. Скажем, у меня есть фрейм данных под названием 'df':

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

Я просматривал код pandas и на самом деле не вижу никаких вариантов для установки ширины столбцов. Есть ли уловка во вселенной, чтобы столбцы автоматически подстраивались под данные? Или есть что-то, что я могу сделать постфактум с файлом xlsx, чтобы настроить ширину столбцов?

(Я использую библиотеку OpenPyXL и создаю файлы .xlsx - если это имеет значение.)

Спасибо.


1
на данный момент не представляется возможным, пожалуйста, откройте вопрос об этом улучшении на github (и, возможно, PR?). не выглядит так сложно сделать.
Джефф

спасибо Джефф, я отправил вопрос. Я не уверен, что у меня будет время действительно погрузиться в кодовую базу pandas, чтобы решить эту проблему, но вы никогда не знаете :)
badideas

да .... видел вашу проблему ..... прокомментируйте проблему, если вам нужна помощь! (по сути, необходимо передать необязательный аргумент to_excel, col_style=dictкоторый , возможно, содержит элементы стиля заголовка col (а не значение по умолчанию, header_styleкоторое, похоже, сейчас жестко закодировано
Джефф,

Ответы:


59

Вдохновленный ответом user6178746 , у меня есть следующее:

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

8
FYI: В моем случае мне нужно использовать «индекс = False» в «df.to_excel (...)» вызов, либо столбцы были выключены на 1
denvar

1
да, мне также пришлось добавить df.to_excel (writer, sheet_name = sheetname, index = False)
Хейкки Пулккинен

2
Если вы не можете использовать индекс = False (потому что у вас есть мультииндексный по строкам), то вы можете получить глубину уровня индекса с df.index.nlevels , а затем использовать это , чтобы добавить к вашему вызову колонки набора: worksheet.set_column(idx+nlevels, idx+nlevels, max_len). В противном случае длина рассчитывается для первого столбца кадра, а затем применяется к первому столбцу в Excel, который, вероятно, является индексом.
ac24 06

1
Для тех, кто все еще ищет этот ответ, enumerate(df)должно быть, enumerate(df.columns)поскольку вы повторяете каждый столбец в df.
Dascienz

2
@Dascienz точно так же, как итерация по a, dictфактически выполняет итерацию по ключам в dict(вам не нужно указывать вручную dict.keys()), итерация по a pd.DataFrameвыполняет итерацию по столбцам. Вам не нужно вручную перебирать df.columns.
Алихаудри

28

Я публикую это, потому что я только что столкнулся с той же проблемой и обнаружил, что в официальной документации для Xlsxwriter и pandas эта функция все еще указана как неподдерживаемая. Я разработал решение, которое решило мою проблему. Я просто перебираю каждый столбец и использую workheet.set_column, чтобы установить ширину столбца == максимальную длину содержимого этого столбца.

Однако одно важное замечание. Это решение не подходит для заголовков столбцов, а только для значений столбцов. Это должно быть легко изменить, если вам нужно вместо этого подогнать заголовки. Надеюсь, это кому-то поможет :)

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
Хорошее решение. Мне нравится, как вы использовали панды вместо другого пакета.

Я думаю, вам нужна ()функция внутри max: `max (column_len (), len (col)) +
2`

21

Вероятно, сейчас нет автоматического способа сделать это, но, поскольку вы используете openpyxl, следующая строка (адаптированная из другого ответа пользователя Bufke о том, как это сделать вручную ) позволяет вам указать разумное значение (в ширине символов):

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

Используемый pandas движок ExcelWriter по умолчанию изменился с 2013 года на Xlsxwriter, который не содержит column_dimensionsатрибутов. Если вы хотите продолжать использовать openpyxl, просто укажите это при создании писателя usingpd.ExcelWriter(excel_filename, engine='openpyxl')
ojdo

@Sunil: проверьте другие ответы, используя Xlsxwriterв качестве движка, чтобы узнать, как указать ширину столбца с сегодняшним движком по умолчанию.
ojdo

21

Есть хороший пакет, который я начал использовать недавно, под названием StyleFrame.

он получает DataFrame и позволяет очень легко его стилизовать ...

по умолчанию ширина столбцов регулируется автоматически.

например:

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

вы также можете изменить ширину столбцов:

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

ОБНОВЛЕНИЕ 1

В версии 1.4 best_fitдобавлен аргумент StyleFrame.to_excel. См. Документацию .

ОБНОВЛЕНИЕ 2

Вот пример кода, который работает для 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()

Пакет StyleFrame может быть простым в использовании, но я не понимаю, как «по умолчанию ширина столбцов регулируется автоматически». Когда я запускаю предоставленный вами образец кода, все столбцы имеют одинаковую ширину, а все три заголовка обертываются. Ваш образец данных также плохо выбран, потому что все они естественно имеют почти одинаковую ширину. Чтобы действительно проиллюстрировать автоматическую настройку, вы должны выбрать действительно широкие данные и некоторые узкие данные. Когда я делаю это для себя, ширина столбцов остается такой же, как и раньше. Никакой регулировки не было.
John Y

Возможно, в какой-то момент истории StyleFrame ширина столбцов была автоматически скорректирована по умолчанию, но, по крайней мере, сегодня вы должны указать столбец или столбцы, которые вы хотите настроить в best_fitпараметре. Кроме того, когда я попробовал это, я получил очень плохие результаты .
Джон Y

ширина кажется отключенной 1 столбец. Я пробовал включать и отключать indexпараметр, но без кубиков.

1
Благодарность! для тех, кто ищет: например, как добавить больше стиля в заголовок: sf.apply_headers_style(Styler(bold=False))мне потребовалось много времени, чтобы понять это. И в заявлении импорта from StyleFrame import StyleFrame, Styler. вот все варианты, кроме жирного: styleframe.readthedocs.io/en/2.0.5/…
VJ

1
@Hagbard, начиная с версии 3, импорт должен быть таким from styleframe import StyleFrame, чтобы соответствовать соглашениям об
именах

11

Используя pandas и xlsxwriter, вы можете выполнить свою задачу, приведенный ниже код будет отлично работать в Python 3.x. Для получения дополнительной информации о работе с XlsxWriter с пандами может быть полезна эта ссылка 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()

5

Динамически регулировать длину всех столбцов

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)

Вручную настройте столбец с помощью имени столбца

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

Вручную настроить столбец с помощью индекса столбца

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

Если что-либо из вышеперечисленного не работает с

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

обязательно установите xlsxwriter:

pip install xlsxwriter

4

Я обнаружил, что более полезно настраивать столбец на основе заголовка столбца, а не содержимого столбца.

С помощью df.columns.values.tolist()я создаю список заголовков столбцов и использую длину этих заголовков для определения ширины столбцов.

Смотрите полный код ниже:

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

На работе я всегда пишу фреймы данных в файлы Excel. Поэтому вместо того, чтобы писать один и тот же код снова и снова, я создал модуль. Теперь я просто импортирую его и использую для записи и форматирования файлов Excel. Однако есть один недостаток: если фрейм данных очень большой, требуется много времени. Итак, вот код:

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


Когда я реплицировал этот код, у меня возникла следующая ошибка: AttributeError: объект 'str' не имеет атрибута 'to_excel'. Думаю, это как-то связано со способом создания dataframe_list. Мой список с 6 именами
фреймов

Да, «dataframe_list» должен содержать фреймы данных, а не имена фреймов данных.
rafat.ch

2

Объединение других ответов и комментариев, а также поддержка мультииндексов:

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

Самое простое решение - указать ширину столбца в методе set_column.

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

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)

1
Только коды не отвечают на вопрос, который вам нужно добавить пояснения или уделить время и прочитать документацию о том, как написать хороший ответ?
Gad

1
Здравствуйте! Хотя этот код может решить вопрос, в том числе объяснение того, как и почему это решает проблему, действительно поможет улучшить качество вашего сообщения и, вероятно, приведет к большему количеству голосов за. Помните, что вы отвечаете на вопрос для будущих читателей, а не только для человека, который задает его сейчас. Пожалуйста , измените свой ответ , чтобы добавить объяснения и дать указание о том , что применять ограничения и допущения.
Брайан

0

Да, есть кое-что, что вы можете сделать постфактум с файлом xlsx, чтобы настроить ширину столбцов. Используйте xlwings для автоматического подбора столбцов. Это довольно простое решение, см. Шесть последних строк кода примера. Преимущество этой процедуры в том, что вам не нужно беспокоиться о размере шрифта, типе шрифта или чем-либо еще. Требование: установка 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()

Работает только в Windows и MacOS, но не в Linux
Гвидо,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.