[スニペット] PythonでCSV→Excel & セルに書式設定

1. 必要なライブラリをインストール
```
pip install pandas xlsxwriter openpyxl
```
2. pandas + XlsxWriter で条件付き書式を使う
```
import pandas as pd

# 1) CSV を読み込む
df = pd.read_csv('input.csv')

# 2) ExcelWriter を使って書き出し（engine='xlsxwriter'）
with pd.ExcelWriter('output.xlsx', engine='xlsxwriter') as writer:
    df.to_excel(writer, index=False, sheet_name='Sheet1')
    workbook  = writer.book
    worksheet = writer.sheets['Sheet1']

    # ───────────────
    # 罫線フォーマット
    # ───────────────
    border_fmt = workbook.add_format({
        'border': 1,            # 全辺に細線
    })
    # 列幅を自動調整しつつ、各セルに罫線を適用
    worksheet.set_column('A:Z', None, border_fmt)

    # ───────────────
    # 条件付き書式：A列が 10 より大きい → 赤背景
    # ───────────────
    red_fill = workbook.add_format({
        'bg_color': '#FFC7CE',  # 薄い赤
    })
    # データ部が A2 から A(行数+1) まで
    last_row = len(df) + 1
    worksheet.conditional_format(
        f'A2:A{last_row}',
        {
            'type':     'cell',
            'criteria': '>',
            'value':    10,
            'format':   red_fill
        }
    )

    # ───────────────
    # 別の条件例：B列が文字列 “NG” → 黄色背景
    # ───────────────
    yellow_fill = workbook.add_format({'bg_color': '#FFEB9C'})
    worksheet.conditional_format(
        f'B2:B{last_row}',
        {
            'type':     'text',
            'criteria': 'containing',
            'value':    'NG',
            'format':   yellow_fill
        }
    )
```
ポイント
- set_column('A:Z', None, border_fmt) で A〜Z 列まで罫線を設定。必要なら範囲を狭めてください
- conditional_format なら Excel 側にルールが残るので、あとで手動変更も可能です

---

3. openpyxl でセル単位にスタイルを手動で当てる
XlsxWriter の条件付き書式では難しい、 より細かい独自ルールがある場合はこちらがおすすめです。
```
import pandas as pd
from openpyxl import Workbook
from openpyxl.styles import PatternFill, Border, Side

# 1) pandas で読み込んでから openpyxl ワークブックを作成
df = pd.read_csv('input.csv')
wb = Workbook()
ws = wb.active
ws.title = 'Sheet1'

# ヘッダー書き込み
ws.append(list(df.columns))
# データ書き込み
for row in df.itertuples(index=False):
    ws.append(row)

# フォーマット定義
red_fill = PatternFill(fill_type='solid', start_color='FFCCCC', end_color='FFCCCC')
thin_side = Side(border_style="thin", color="000000")
border   = Border(left=thin_side, right=thin_side, top=thin_side, bottom=thin_side)

# 2) セルを１つずつ巡回してスタイル設定
for row in ws.iter_rows(min_row=2, max_row=ws.max_row,
                        min_col=1, max_col=ws.max_column):
    for cell in row:
        # 数値が 10 より大きければ赤背景
        if isinstance(cell.value, (int, float)) and cell.value > 10:
            cell.fill = red_fill
        # どんなセルにも罫線を
        cell.border = border

# 3) 保存
wb.save('styled_openpyxl.xlsx')
```
ポイント
- PatternFill で好きな色の塗りつぶしが可能
- Border／Side で罫線スタイルを細かく制御
- ループ内で if による任意の条件判定ができる

---
テスト用ダミーデータ
```csv
No,都道府県名,最高気温,最低気温,日の出時刻,日没時刻
1,北海道,23.6,12.7,07:00,17:09
2,青森県,31.2,30.4,05:17,18:43
3,岩手県,17.5,11.7,06:17,17:12
4,宮城県,15.4,6.9,05:53,17:56
5,秋田県,19.6,7.5,06:28,17:24
6,山形県,32.9,29.6,05:36,17:25
7,福島県,17.1,10.0,06:30,18:50
8,茨城県,28.8,7.3,06:05,18:22
9,栃木県,19.0,14.3,05:48,17:43
10,群馬県,31.8,13.0,06:23,18:46
11,埼玉県,24.2,13.7,06:49,17:18
12,千葉県,26.7,16.2,06:24,17:55
13,東京都,27.5,24.3,05:40,18:34
14,神奈川県,18.5,9.2,05:11,18:47
15,新潟県,16.1,14.4,05:46,18:04
16,富山県,19.0,17.8,05:26,16:55
17,石川県,34.2,12.3,06:36,17:42
18,福井県,33.0,17.4,05:00,16:41
19,山梨県,24.4,11.6,05:03,18:27
20,長野県,19.5,16.9,05:08,18:17
21,岐阜県,18.6,5.4,06:36,18:42
22,静岡県,19.9,18.2,05:16,17:50
23,愛知県,22.2,19.9,06:33,17:55
24,三重県,21.8,20.3,05:56,17:37
25,滋賀県,17.4,14.5,06:56,16:55
26,京都府,26.5,8.8,05:02,18:24
27,大阪府,24.2,16.9,05:01,17:06
28,兵庫県,26.3,6.7,06:18,16:47
29,奈良県,17.5,11.3,06:27,18:46
30,和歌山県,24.2,12.1,05:57,16:53
31,鳥取県,17.1,6.9,05:44,16:58
32,島根県,16.0,10.5,06:14,18:08
33,岡山県,26.4,16.1,06:03,16:57
34,広島県,20.4,18.8,06:50,17:17
35,山口県,31.1,25.4,06:47,17:22
36,徳島県,26.7,7.2,05:22,17:32
37,香川県,26.7,14.0,05:03,16:55
38,愛媛県,17.0,8.0,05:49,18:12
39,高知県,23.5,13.3,06:22,18:17
40,福岡県,28.2,24.0,06:25,17:12
41,佐賀県,17.4,12.5,06:50,18:58
42,長崎県,15.3,13.4,05:20,17:43
43,熊本県,18.2,8.1,06:27,17:37
44,大分県,30.6,11.8,06:14,17:56
45,宮崎県,31.9,13.5,05:02,18:42
46,鹿児島県,32.8,8.0,06:42,18:09
47,沖縄県,17.7,8.8,05:18,17:23
```
不定形なCSVの場合
```
出力条件,,,,,
国,,日本
取得日,,20250425
,
No,都道府県名,最高気温,最低気温,日の出時刻,日没時刻
1,北海道,23.6,12.7,07:00,17:09
2,青森県,31.2,30.4,05:17,18:43
--省略--
```
```
import csv
from openpyxl import Workbook
from openpyxl.styles import PatternFill, Border, Side

# 1) CSV を全部読み込む
with open('input.csv', newline='', encoding='utf-8') as f:
    reader = csv.reader(f)
    rows = list(reader)

# 2) Workbook 作成＆シート取得
wb = Workbook()
ws = wb.active

# 3) 全行を順に書き出し（1行目～）
for row in rows:
    ws.append(row)

# 4) 書式定義
red_fill = PatternFill(fill_type='solid', start_color='FFC7CE', end_color='FFC7CE')
thin_side = Side(border_style='thin', color='000000')
border = Border(left=thin_side, right=thin_side, top=thin_side, bottom=thin_side)

# 5) データ部（6行目＝Excel上の row=6 から最終行まで）にだけ書式を適用
max_row = len(rows)
# 5行目（Excel行＝5）がヘッダーなので、その下（6行目）から
for row_cells in ws.iter_rows(min_row=6, max_row=max_row,
                              min_col=1, max_col=len(rows[4])):  # rows[4] はヘッダー行の列数
    for cell in row_cells:
        # 罫線
        cell.border = border
        # 数値かどうか試して、10超なら赤背景
        try:
            if float(cell.value) > 10:
                cell.fill = red_fill
        except (TypeError, ValueError):
            pass

# 6) 保存
wb.save('styled_output.xlsx')
```
```
# 書式定義
red_fill = PatternFill(fill_type='solid', start_color='FFC7CE', end_color='FFC7CE')
thin = Side(border_style='thin', color='000000')
border = Border(left=thin, right=thin, top=thin, bottom=thin)

# データ部：Excelの6行目から最終行まで（ヘッダーは5行目まで）
for row_cells in ws.iter_rows(min_row=6, max_row=ws.max_row,
                              min_col=1, max_col=ws.max_column):
    for cell in row_cells:
        # まずは全セルに罫線
        cell.border = border

        # 列番号を取得（openpyxl >= 2.6 なら .column で整数が返る）
        col_idx = cell.column

        # 11列目以降だけ処理
        if col_idx > 10:
            # グループ n を計算（0:11～20列, 1:21～30列, …）
            n = (col_idx - 11) // 10
            # グループ内の相対位置 r を計算（1～10）
            #    11列 → r=1, 12列 → r=2, … 20列 → r=10
            r = col_idx - 10 - 10*n

            # r==2 または r>=4 のときだけ色塗り
            if r == 2 or (4 <= r <= 10):
                cell.fill = red_fill
```
```
# --- 前提：Workbook, ws, red_fill, border などは既存の定義のまま ---

for row_cells in ws.iter_rows(min_row=6, max_row=ws.max_row,
                              min_col=1, max_col=ws.max_column):
    for cell in row_cells:
        # まずは全セルに罫線
        cell.border = border

        col_idx = cell.column
        # 11列目以降のみ処理
        if col_idx > 10:
            # グループ n を計算（0:11～20列, 1:21～30列, …）
            n = (col_idx - 11) // 10
            # グループ内の相対位置 r を計算（1～10）
            r = col_idx - 10 - 10 * n

            # r==1 のセルを基準値として取得
            # グループ先頭列 = 10 + 10*n + 1
            base_col = 10 + 10 * n + 1
            base_cell = ws.cell(row=cell.row, column=base_col)

            # 値を数値に変換して差分をチェック
            try:
                current = float(cell.value)
                base    = float(base_cell.value)
                if current - base >= 5.5:
                    cell.fill = red_fill
            except (TypeError, ValueError):
                # 非数値セルはスキップ
                pass
```
色塗りロジック追加
流れのまとめ
①書式定義：pink_fill、blue_fill、green_fill を追加
②ループ内で r == 3 判定
1. 同じグループの r=4～10 を走査
2. 差分に応じて 2,1,0,-1,-2 ポイント付与
3. 合計ポイントに従い、r == 3 セルに塗りつぶし
4. 既存の r == 2／r >= 4 ロジック はそのまま elif に

これで、r==3 の列にはグループの傾向を色で可視化できるようになります。
```
from openpyxl.styles import PatternFill, Border, Side

# ────────────
# 書式定義
# ────────────
red_fill   = PatternFill(fill_type='solid', start_color='FFC7CE', end_color='FFC7CE')
pink_fill  = PatternFill(fill_type='solid', start_color='FFC0CB', end_color='FFC0CB')  # ピンク
blue_fill  = PatternFill(fill_type='solid', start_color='ADD8E6', end_color='ADD8E6')  # 青
green_fill = PatternFill(fill_type='solid', start_color='90EE90', end_color='90EE90')  # 緑

thin_side = Side(border_style='thin', color='000000')
border    = Border(left=thin_side, right=thin_side, top=thin_side, bottom=thin_side)

# ────────────
# データ行のループ
# ────────────
for row_cells in ws.iter_rows(min_row=6, max_row=ws.max_row,
                              min_col=1, max_col=ws.max_column):
    for cell in row_cells:
        # ① まずは全セルに罫線
        cell.border = border

        col_idx = cell.column
        if col_idx <= 10:
            continue

        # グループ番号 n と相対位置 r を計算
        n = (col_idx - 11) // 10
        r = col_idx - 10 - 10 * n

        # 基準値セル（r==1）の取得
        base_col  = 10 + 10 * n + 1
        base_cell = ws.cell(row=cell.row, column=base_col)

        # ────────────
        # r==3 のとき：グループ内 r=4～10 のスコアを合計し、その合計で色分け
        # ────────────
        if r == 3:
            total = 0
            for offset in range(4, 11):
                c = ws.cell(row=cell.row, column=10 + 10 * n + offset)
                try:
                    diff = float(c.value) - float(base_cell.value)
                except (TypeError, ValueError):
                    continue
                if diff >= 5.5:
                    pts = 2
                elif diff >= 2.5:
                    pts = 1
                elif diff < -5.5:
                    pts = -2
                elif diff < -2.5:
                    pts = -1
                else:
                    pts = 0
                total += pts

            # 合計ポイントで色分け
            if total > 5:
                cell.fill = red_fill
            elif total > 2:
                cell.fill = pink_fill
            elif total > -2:
                # 塗りつぶしなし
                pass
            elif total > -4:
                cell.fill = blue_fill
            else:
                cell.fill = green_fill

        # ────────────
        # 既存ロジック：r==2 または r>=4 のセル単体の判定
        # ────────────
        elif r == 2 or (4 <= r <= 10):
            try:
                current = float(cell.value)
                base    = float(base_cell.value)
                if current - base >= 5.5:
                    cell.fill = red_fill
            except (TypeError, ValueError):
                pass
```