web-dev-qa-db-ja.com

xlsxwriterで自動調整列をシミュレートする

PythonのxlsxwriterでExcelの自動調整機能をシミュレートしたいと思います。このURLによると、直接サポートされていません: http://xlsxwriter.readthedocs.io/worksheet.html

ただし、シート上の各セルをループして列の最大サイズを決定し、worksheet.set_column(row、col、width)を使用して幅を設定するのは非常に簡単です。

私がこれを書くのを妨げている複雑さは次のとおりです。

  1. そのURLは、set_columnの3番目の引数の単位を指定しません。
  2. セルに挿入するアイテムの幅を測定する方法が見つかりません。
  3. xlsxwriterには、特定のセルを読み戻す方法がないようです。これは、セルを書き込むときに各セルの幅を追跡する必要があることを意味します。すべてのセルをループすることができれば、一般的なルーチンを書くことができた方が良いでしょう。
25
Michael Potter

一般的な規則として、列の幅は、列内の最も長い文字列のサイズよりも少し大きくする必要があります。 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)
26
Cole Diamond

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    
2
ascripter

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]
1
Soumitra

行と列の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
Sebastian

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列。

0
karolyi