Apakah ada cara untuk menyesuaikan lebar kolom Excel secara otomatis dengan pandas.ExcelWriter?

105

Saya diminta untuk membuat beberapa laporan Excel. Saat ini saya menggunakan pandas cukup banyak untuk data saya, jadi tentu saja saya ingin menggunakan metode pandas.ExcelWriter untuk menghasilkan laporan ini. Namun lebar kolom tetap menjadi masalah.

Kode yang saya miliki sejauh ini cukup sederhana. Katakanlah saya memiliki dataframe yang disebut 'df':

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

Saya melihat-lihat kode pandas, dan saya tidak benar-benar melihat opsi untuk mengatur lebar kolom. Apakah ada trik di luar sana di alam semesta untuk membuatnya sedemikian rupa sehingga kolom menyesuaikan secara otomatis dengan data? Atau apakah ada sesuatu yang dapat saya lakukan setelah fakta ke file xlsx untuk menyesuaikan lebar kolom?

(Saya menggunakan pustaka OpenPyXL, dan membuat file .xlsx - jika ada bedanya.)

Terima kasih.

ide buruk
sumber
1
saat ini tampaknya tidak memungkinkan, buka masalah untuk peningkatan ini di github (dan mungkin PR?). tidak terlihat sulit untuk dilakukan.
Jeff
terima kasih Jeff, saya telah mengirimkan masalah ini. Saya tidak yakin apakah saya akan punya waktu untuk benar-benar menyelami basis kode panda untuk menyelesaikannya, tetapi Anda tidak pernah tahu :)
badideas
ya .... melihat masalah Anda ..... mengomentari masalah jika Anda butuh bantuan! (pada dasarnya perlu memberikan argumen opsional ke to_excel, mungkin col_style=dictyang berisi elemen gaya header col (daripada default header_styleyang tampaknya sulit dikodekan sekarang
Jeff

Jawaban:

59

Terinspirasi oleh jawaban user6178746 , saya memiliki yang berikut:

# 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.dll
sumber
8
FYI: Dalam kasus saya, saya perlu menggunakan "index = False" dalam panggilan "df.to_excel (...)", atau kolomnya dimatikan 1
denvar
1
ya, saya juga harus menambahkan df.to_excel (writer, sheet_name = sheetname, index = False)
Heikki Pulkkinen
2
Jika Anda tidak dapat menggunakan indeks = False (karena Anda memiliki multiindex pada baris), maka Anda bisa mendapatkan kedalaman tingkat indeks dengan df.index.nlevels dan kemudian menggunakan ini untuk menambahkan ke set kolom panggilan Anda: worksheet.set_column(idx+nlevels, idx+nlevels, max_len). Jika tidak, panjangnya dihitung untuk kolom pertama frame, dan kemudian diterapkan ke kolom pertama di excel, yang mungkin merupakan indeks.
ac24
1
Bagi siapa pun yang masih mencari jawaban ini, enumerate(df)seharusnya enumerate(df.columns)karena Anda mengulang setiap kolom dalam df.
Dascienz
2
@Dascienz dengan cara yang sama melakukan iterasi di atas dictsebenarnya iterasi atas kunci di dict(Anda tidak perlu mengatakannya secara manual dict.keys()), melakukan iterasi di pd.DataFrameatas kolom yang berulang. Anda tidak perlu mengulang secara manual df.columns.
alichaudry
28

Saya memposting ini karena saya baru saja mengalami masalah yang sama dan menemukan bahwa dokumentasi resmi untuk Xlsxwriter dan panda masih memiliki fungsi ini terdaftar sebagai tidak didukung. Saya meretas solusi yang menyelesaikan masalah yang saya alami. Saya pada dasarnya hanya mengulang melalui setiap kolom dan menggunakan worksheet.set_column untuk mengatur lebar kolom == panjang maksimal dari isi kolom itu.

Namun, ada satu catatan penting. Solusi ini tidak sesuai dengan tajuk kolom, hanya nilai kolomnya. Itu harus menjadi perubahan yang mudah meskipun jika Anda perlu menyesuaikan header saja. Semoga ini bisa membantu seseorang :)

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
sumber
1
Solusi yang bagus. Saya suka bagaimana Anda menggunakan panda, bukan paket lain.
Saya pikir Anda perlu ()di dalam fungsi maks: `max (column_len (), len (col)) + 2`
Serdia
21

Mungkin tidak ada cara otomatis untuk melakukannya saat ini, tetapi saat Anda menggunakan openpyxl, baris berikut (diadaptasi dari jawaban lain oleh pengguna Bufke tentang cara melakukannya secara manual ) memungkinkan Anda untuk menentukan nilai yang waras (dalam lebar karakter):

writer.sheets['Summary'].column_dimensions['A'].width = 15
ojdo
sumber
Panda mesin ExcelWriter default yang digunakan telah berubah sejak 2013 menjadi Xlsxwriter, yang tidak berisi column_dimensionsatribut. Jika Anda ingin tetap menggunakan openpyxl, cukup tentukan saat membuat penulis menggunakanpd.ExcelWriter(excel_filename, engine='openpyxl')
ojdo
@ Sunil: periksa jawaban lain yang digunakan Xlsxwritersebagai mesin untuk melihat bagaimana menentukan lebar kolom dengan mesin default hari ini.
ojdo
21

Ada paket bagus yang saya mulai gunakan baru-baru ini yang disebut StyleFrame.

itu mendapat DataFrame dan memungkinkan Anda untuk menatanya dengan sangat mudah ...

secara default, lebar kolom diatur secara otomatis.

sebagai contoh:

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

Anda juga dapat mengubah lebar kolom:

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

UPDATE 1

Dalam versi 1.4, best_fitargumen ditambahkan ke StyleFrame.to_excel. Lihat dokumentasinya .

UPDATE 2

Berikut adalah contoh kode yang berfungsi untuk 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
sumber
Paket StyleFrame mungkin mudah digunakan, tetapi saya tidak melihat bagaimana "secara default lebar kolom menyesuaikan secara otomatis". Ketika saya menjalankan contoh kode yang Anda berikan, semua kolom memiliki lebar yang sama, dan ketiga header dibungkus. Data sampel Anda juga tidak dipilih dengan baik, karena biasanya lebarnya hampir sama. Untuk benar-benar menggambarkan penyesuaian otomatis, Anda harus memilih beberapa data yang sangat luas dan beberapa data yang sempit. Saat saya melakukan ini untuk diri saya sendiri, lebar kolom masih sama persis seperti sebelumnya. Tidak ada penyesuaian apapun.
John Y
Mungkin pada satu titik dalam sejarah StyleFrame, lebar kolom secara otomatis disesuaikan secara default, tetapi setidaknya hari ini, Anda harus menentukan kolom atau kolom yang ingin Anda sesuaikan dalam best_fitparameter. Juga, ketika saya mencoba ini, saya mendapatkan hasil yang sangat buruk .
John Y
lebar tampaknya dari 1 kolom. Saya mencoba mengaktifkan dan menonaktifkan indexparameter tetapi tidak ada dadu.
1
Terima kasih! bagi mereka yang mencari: Bagaimana Anda menambahkan lebih banyak gaya ke tajuk misalnya: sf.apply_headers_style(Styler(bold=False))saya butuh waktu lama untuk mengetahuinya. Dan dalam pernyataan impor from StyleFrame import StyleFrame, Styler,. inilah semua opsi selain dari huruf tebal: styleframe.readthedocs.io/en/2.0.5/…
Nikhil VJ
1
@Hagbard pada versi 3, impor harus from styleframe import StyleFramesesuai dengan konvensi nama PEP8
DeepSpace
11

Dengan menggunakan pandas dan xlsxwriter Anda dapat melakukan tugas Anda, kode di bawah ini akan bekerja dengan sempurna di Python 3.x. Untuk detail lebih lanjut tentang bekerja dengan XlsxWriter dengan panda, tautan ini mungkin berguna 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
sumber
5

Sesuaikan semua panjang kolom secara dinamis

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)

Sesuaikan kolom secara manual menggunakan Nama Kolom

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

Sesuaikan kolom secara manual menggunakan Indeks Kolom

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

Jika salah satu hal di atas gagal dengan

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

pastikan untuk menginstal xlsxwriter:

pip install xlsxwriter
Giorgos Myrianthous
sumber
4

Saya menemukan bahwa lebih bermanfaat untuk menyesuaikan kolom dengan berdasarkan tajuk kolom daripada konten kolom.

Menggunakan df.columns.values.tolist()I menghasilkan daftar tajuk kolom dan menggunakan panjang tajuk ini untuk menentukan lebar kolom.

Lihat kode lengkap di bawah ini:

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

Di tempat kerja, saya selalu menulis kerangka data ke file excel. Jadi, alih-alih menulis kode yang sama berulang kali, saya telah membuat modulus. Sekarang saya hanya mengimpor dan menggunakannya untuk menulis dan memformat file excel. Namun ada satu sisi negatifnya, dibutuhkan waktu lama jika kerangka data ekstra besar. Jadi inilah kodenya:

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
sumber
Saya mendapat kesalahan berikut ketika saya mereplikasi kode ini: AttributeError: objek 'str' tidak memiliki atribut 'to_excel'. Ia pikir itu ada hubungannya dengan cara "dataframe_list" dibuat. Milik saya adalah daftar dengan 6 nama
kerangka data
Ya, "dataframe_list" harus memiliki dataframe dan bukan nama dataframe.
rafat.ch
2

Menggabungkan jawaban dan komentar lain dan juga mendukung multi-indeks:

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

Solusi termudah adalah menentukan lebar kolom dalam metode set_column.

    for worksheet in writer.sheets.values():
        worksheet.set_column(0,last_column_value, required_width_constant)
Ashish Jith
sumber
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
sumber
1
kode hanya tidak menjawab pertanyaan Anda harus menambahkan beberapa penjelasan atau meluangkan waktu dan membaca dokumentasi tentang Bagaimana cara menulis jawaban yang baik?
Gad
1
Halo! Meskipun kode ini dapat menyelesaikan pertanyaan, termasuk penjelasan tentang bagaimana dan mengapa ini menyelesaikan masalah akan sangat membantu untuk meningkatkan kualitas posting Anda, dan mungkin menghasilkan lebih banyak suara. Ingatlah bahwa Anda menjawab pertanyaan untuk pembaca di masa depan, bukan hanya orang yang bertanya sekarang. Harap edit jawaban Anda untuk menambahkan penjelasan dan memberikan indikasi batasan dan asumsi apa yang berlaku.
Brian
0

Ya, ada sesuatu yang dapat Anda lakukan setelah fakta ke file xlsx untuk menyesuaikan lebar kolom. Gunakan xlwings untuk mengisi otomatis kolom. Ini solusi yang cukup sederhana, lihat enam baris terakhir dari kode contoh. Keuntungan dari prosedur ini adalah Anda tidak perlu khawatir tentang ukuran font, jenis font, atau hal lainnya. Persyaratan: Instalasi 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
sumber
Hanya bekerja di Windows dan MacOS, bukan di Linux
Guido