PythonのxlsxwriterでExcelの自動調整機能をシミュレートしたいと思います。このURLによると、直接サポートされていません: http://xlsxwriter.readthedocs.io/worksheet.html
ただし、シート上の各セルをループして列の最大サイズを決定し、worksheet.set_column(row、col、width)を使用して幅を設定するのは非常に簡単です。
私がこれを書くのを妨げている複雑さは次のとおりです。
一般的な規則として、列の幅は、列内の最も長い文字列のサイズよりも少し大きくする必要があります。 xlsxwriter列の1単位のwithは、1文字の幅とほぼ同じです。そのため、各列をその列の最大文字数に設定することにより、自動調整をシミュレートできます。
たとえば、pandas dataframesとxlsxwriterで作業するときは、以下のコードを使用する傾向があります。
最初にインデックスの最大幅を見つけます。これは、Excelでレンダリングされたデータフレームに対して常にpandasの左列です。次に、すべての値と列名の最大値を返します。残りの列は左から右に移動します。
使用しているデータに合わせてこのコードを適合させることはそれほど難しくないはずです。
def get_col_widths(dataframe):
# First we find the maximum length of the index column
idx_max = max([len(str(s)) for s in dataframe.index.values] + [len(str(dataframe.index.name))])
# Then, we concatenate this to the max of the lengths of column name and its values for each column, left to right
return [idx_max] + [max([len(str(s)) for s in dataframe[col].values] + [len(col)]) for col in dataframe.columns]
for i, width in enumerate(get_col_widths(dataframe)):
worksheet.set_column(i, i, width)
xlsxwriterのGithubサイト で見つかったAutofitをシミュレートする別の回避策があります。水平テキストのおおよそのサイズ(列幅)または90度回転したテキスト(行の高さ)を返すように変更しました。
from PIL import ImageFont
def get_cell_size(value, font_name, font_size, dimension="width"):
""" value: cell content
font_name: The name of the font in the target cell
font_size: The size of the font in the target cell """
font = ImageFont.truetype(font_name, size=font_size)
(size, h) = font.getsize(str(value))
if dimension == "height":
return size * 0.92 # fit value experimentally determined
return size * 0.13 # fit value experimentally determined
これは、太字テキストや、テキストサイズに影響する可能性のある他のフォーマット要素には対応していません。それ以外の場合は、かなりうまく機能します。
自動調整用の列の幅を見つけるには:
def get_col_width(data, font_name, font_size, min_width=1):
""" Assume 'data' to be an iterable (rows) of iterables (columns / cells)
Also, every cell is assumed to have the same font and font size.
Returns a list with the autofit-width per column """
colwidth = [min_width for col in data[0]]
for x, row in enumerate(data):
for y, value in enumerate(row):
colwidth[y] = max(colwidth[y], get_cell_size(value, font_name, font_size))
return colwidth
Cole Diamondの答え は素晴らしい。マルチインデックスの行と列を処理するためにサブルーチンを更新しました。
def get_col_widths(dataframe):
# First we find the maximum length of the index columns
idx_max = [max([len(str(s)) for s in dataframe.index.get_level_values(idx)] + [len(str(idx))]) for idx in dataframe.index.names]
# Then, we concatenate this to the max of the lengths of column name and its values for each column, left to right
return idx_max + [max([len(str(s)) for s in dataframe[col].values] + \
[len(str(x)) for x in col] if dataframe.columns.nlevels > 1 else [len(str(col))]) for col in dataframe.columns]
行と列のMultiIndexをサポートするコードのバージョンを次に示します-それはきれいではありませんが、私にとってはうまくいきます。 @ cole-diamondの回答を拡張します。
def _xls_make_columns_wide_enough(dataframe, worksheet, padding=1.1, index=True):
def get_col_widths(dataframe, padding, index):
max_width_idx = []
if index and isinstance(dataframe.index, pd.MultiIndex):
# Index name lengths
max_width_idx = [len(v) for v in dataframe.index.names]
# Index value lengths
for column, content in enumerate(dataframe.index.levels):
max_width_idx[column] = max(max_width_idx[column],
max([len(str(v)) for v in content.values]))
Elif index:
max_width_idx = [
max([len(str(s))
for s in dataframe.index.values] + [len(str(dataframe.index.name))])
]
if isinstance(dataframe.columns, pd.MultiIndex):
# Take care of columns - headers first.
max_width_column = [0] * len(dataframe.columns.get_level_values(0))
for level in range(len(dataframe.columns.levels)):
values = dataframe.columns.get_level_values(level).values
max_width_column = [
max(v1, len(str(v2))) for v1, v2 in Zip(max_width_column, values)
]
# Now content.
for idx, col in enumerate(dataframe.columns):
max_width_column[idx] = max(max_width_column[idx],
max([len(str(v)) for v in dataframe[col].values]))
else:
max_width_column = [
max([len(str(s)) for s in dataframe[col].values] + [len(col)])
for col in dataframe.columns
]
return [round(v * padding) for v in max_width_idx + max_width_column]
for i, width in enumerate(get_col_widths(dataframe, padding, index)):
worksheet.set_column(i, i, width)
1つのワークシートを調べて、フィールドの長さを自動設定する私のバージョン:
from typing import Optional
from xlsxwriter.worksheet import (
Worksheet, cell_number_Tuple, cell_string_Tuple)
def get_column_width(worksheet: Worksheet, column: int) -> Optional[int]:
"""Get the max column width in a `Worksheet` column."""
strings = getattr(worksheet, '_ts_all_strings', None)
if strings is None:
strings = worksheet._ts_all_strings = sorted(
worksheet.str_table.string_table,
key=worksheet.str_table.string_table.__getitem__)
lengths = set()
for row_id, colums_dict in worksheet.table.items(): # type: int, dict
data = colums_dict.get(column)
if not data:
continue
if type(data) is cell_string_Tuple:
iter_length = len(strings[data.string])
if not iter_length:
continue
lengths.add(iter_length)
continue
if type(data) is cell_number_Tuple:
iter_length = len(str(data.number))
if not iter_length:
continue
lengths.add(iter_length)
if not lengths:
return None
return max(lengths)
def set_column_autowidth(worksheet: Worksheet, column: int):
"""
Set the width automatically on a column in the `Worksheet`.
!!! Make sure you run this function AFTER having all cells filled in
the worksheet!
"""
maxwidth = get_column_width(worksheet=worksheet, column=column)
if maxwidth is None:
return
worksheet.set_column(first_col=column, last_col=column, width=maxwidth)
単にset_column_autowidth
列。