【Excel VBA】CSVファイルの列順を自在に変換!データ加工を自動化するマクロ

Excel・VBA

はじめに:なぜCSVの列順変更が必要なのか?

CSV(Comma Separated Values)データは、システム間のデータ連携やデータ分析において広く利用されています。しかし、システムやツールの要件によっては、特定の列順でのデータ受け渡しが求められることが少なくありません。

例えば、あるシステムからは「商品ID,商品名,価格」の順で出力されるCSVを、別のシステムが「商品名,価格,商品ID」の順でしか受け付けない、といったケースです。このような場合、手作業でCSVファイルを編集すると、膨大な時間と手間がかかり、ヒューマンエラーのリスクも高まります。

この記事では、Excel VBA(Visual Basic for Applications)を使って、CSVファイルの列順を自由に組み替え、新しい形式のCSVファイルとして出力する方法を解説します。一度マクロを作成してしまえば、何度でも再利用でき、業務効率を大幅に向上させることができます。

準備:サンプルデータと要件の確認

入力CSVファイルの例

まずは、変換元のCSVファイルがどのような形式かを確認しましょう。以下のような商品データを含むCSVファイルがあると仮定します。

商品ID,商品名,価格,カテゴリ,在庫数
1001,りんご,150,果物,50
1002,みかん,120,果物,80
1003,バナナ,100,果物,120

出力したいCSVファイルの例(列順変更後)

上記のCSVファイルを、例えば「商品名,カテゴリ,商品ID,価格」の順に変換したいとします。元のCSVには「在庫数」の列がありますが、これは不要なため出力しないものとします。

商品名,カテゴリ,商品ID,価格
りんご,果物,1001,150
みかん,果物,1002,120
バナナ,果物,1003,100

Excel VBAでCSV列順を変更するマクロの実装

マクロの全体像

今回作成するマクロは、以下のステップで処理を行います。

  • 変換元のCSVファイルをユーザーに選択させる。
  • 変換後のCSVファイルの保存先とファイル名をユーザーに指定させる。
  • CSVファイルを1行ずつ読み込む。
  • 最初の行(ヘッダー行)から、各列のヘッダー名とそのインデックスをマッピングする。
  • ユーザーが指定した新しい列順(ヘッダー名)に基づいて、出力する列のインデックスを決定する。
  • データ行をカンマで分割し、新しい列順でデータを再構築する。
  • 再構築したデータを新しいCSVファイルに書き出す。

VBAサンプルコード

以下のVBAコードを標準モジュールに貼り付けてください。

Option Explicit

Sub ConvertCsvColumnOrder()

    Dim filePath As String
    Dim newFilePath As String
    Dim fso As Object ' FileSystemObject
    Dim tsIn As Object ' TextStream for input
    Dim tsOut As Object ' TextStream for output
    Dim line As String
    Dim dataArray As Variant
    Dim newOrder() As Long ' 新しい列順のインデックス (0始まり)
    Dim headerMap As Object ' ヘッダー名とインデックスのマッピング
    Dim headerLine As String
    Dim headers() As String
    Dim i As Long
    Dim newHeaderLine As String
    Dim newRowData As String

    ' --- 1. 変換元のCSVファイルを選択 --- 
    With Application.FileDialog(msoFileDialogFilePicker)
        .Title = "変換元のCSVファイルを選択してください"
        .Filters.Add "CSVファイル", "*.csv", 1
        .AllowMultiSelect = False
        If .Show = -1 Then
            filePath = .SelectedItems(1)
        Else
            MsgBox "ファイル選択がキャンセルされました。", vbInformation
            Exit Sub
        End If
    End With

    ' --- 2. 変換後のCSVファイルの保存先とファイル名を指定 --- 
    With Application.FileDialog(msoFileDialogSaveAs)
        .Title = "変換後のCSVファイルを保存してください"
        .Filters.Add "CSVファイル", "*.csv", 1
        .FileName = Replace(filePath, ".csv", "_converted.csv") ' デフォルトファイル名を提案
        If .Show = -1 Then
            newFilePath = .SelectedItems(1)
        Else
            MsgBox "保存がキャンセルされました。", vbInformation
            Exit Sub
        End If
    End With

    Set fso = CreateObject("Scripting.FileSystemObject")

    ' --- 3. 入力ファイルを開く --- 
    Set tsIn = fso.OpenTextFile(filePath, 1) ' ForReading

    ' --- 4. 出力ファイルを作成 --- 
    Set tsOut = fso.CreateTextFile(newFilePath, True) ' True: 上書き

    ' --- 5. ヘッダー行を読み込み、列順をマッピング --- 
    If Not tsIn.AtEndOfStream Then
        headerLine = tsIn.ReadLine
        headers = Split(headerLine, ",") ' ヘッダーをカンマで分割

        Set headerMap = CreateObject("Scripting.Dictionary")
        For i = LBound(headers) To UBound(headers)
            headerMap(Trim(headers(i))) = i ' ヘッダー名 -> インデックスをマッピング
        Next i

        ' --- 6. ここで新しい列順をヘッダー名で指定 --- 
        ' 例: "商品名", "カテゴリ", "商品ID", "価格" の順で出力したい場合
        Dim desiredHeaderOrder As Variant
        desiredHeaderOrder = Array("商品名", "カテゴリ", "商品ID", "価格") ' 出力したいヘッダーの順を文字列で指定

        ReDim newOrder(LBound(desiredHeaderOrder) To UBound(desiredHeaderOrder))
        For i = LBound(desiredHeaderOrder) To UBound(desiredHeaderOrder)
            If headerMap.Exists(desiredHeaderOrder(i)) Then
                newOrder(i) = headerMap(desiredHeaderOrder(i)) ' 指定されたヘッダー名に対応する元のインデックスを取得
            Else
                MsgBox "指定されたヘッダー '" & desiredHeaderOrder(i) & "' が入力ファイルに見つかりません。処理を中断します。", vbCritical
                tsIn.Close
                tsOut.Close
                Exit Sub
            End If
        Next i

        ' 新しいヘッダー行を作成して出力ファイルに書き込む
        newHeaderLine = Join(desiredHeaderOrder, ",")
        tsOut.WriteLine newHeaderLine
    Else
        MsgBox "入力ファイルが空です。処理を中断します。", vbCritical
        tsIn.Close
        tsOut.Close
        Exit Sub
    End If

    ' --- 7. データ行を読み込み、列順を変更して書き出す --- 
    Do Until tsIn.AtEndOfStream
        line = tsIn.ReadLine
        dataArray = Split(line, ",") ' 行データをカンマで分割

        ' 新しい列順でデータを再構築するための配列
        Dim tempArray() As String
        ReDim tempArray(LBound(newOrder) To UBound(newOrder))

        For i = LBound(newOrder) To UBound(newOrder)
            If newOrder(i) <= UBound(dataArray) Then ' 既存のデータ範囲内か確認
                tempArray(i) = dataArray(newOrder(i))
            Else
                tempArray(i) = "" ' データが存在しない場合は空文字
            End If
        Next i

        newRowData = Join(tempArray, ",") ' 再構築したデータをカンマ区切り文字列に結合
        tsOut.WriteLine newRowData ' 出力ファイルに書き込む
    Loop

    ' --- 8. ファイルを閉じる --- 
    tsIn.Close
    tsOut.Close

    MsgBox "CSVファイルの列順変換が完了しました!" & vbCrLf & "保存先: " & newFilePath, vbInformation

End Sub

コードの解説

このマクロは、以下の主要なステップでCSVファイルの列順変換を行います。

  • ファイル選択ダイアログ: Application.FileDialog を使用して、ユーザーが変換元のCSVファイルと変換後のCSVファイルの保存先をGUIで簡単に選択できるようにしています。これにより、マクロの汎用性が高まります。
  • FileSystemObjectの利用: CreateObject("Scripting.FileSystemObject") を使うことで、VBA標準のファイル操作関数よりも柔軟かつ効率的にテキストファイル(CSV)の読み書きを行うことができます。
  • ヘッダー解析と列順マッピング:
    • まず、入力CSVの1行目(ヘッダー行)を読み込み、Split 関数でカンマ区切りで分割し、各ヘッダー名を配列 headers に格納します。
    • Scripting.Dictionary オブジェクトを使用して、各ヘッダー名とそれに対応する元の列のインデックス(0始まり)をマッピングします。これにより、「商品ID」が元のCSVの何列目にあるかを動的に取得できます。
    • desiredHeaderOrder という配列に、出力したいCSVのヘッダー名とその順序を文字列で指定します。この部分を編集することで、出力する列とその順序を自由にカスタマイズできます。
    • desiredHeaderOrder に基づいて、元のCSVのどの列をどの順序で出力するかを示すインデックス配列 newOrder を動的に作成します。存在しないヘッダーが指定された場合はエラーメッセージを表示して処理を中断します。
    • 新しいヘッダー行を生成し、出力ファイルに書き込みます。
  • データ行の処理:
    • ヘッダー解析後、Do Until tsIn.AtEndOfStream ループを使って、CSVファイルを1行ずつ最後まで読み込みます。
    • 各データ行も Split 関数でカンマ区切りで分割し、dataArray に格納します。
    • newOrder 配列で定義された新しい列順に従って、dataArray から必要なデータを抽出し、tempArray に格納し直します。これにより、列の並び替えが実現されます。
    • Join 関数を使って、tempArray の要素をカンマで結合し、新しいCSVの1行データとして出力ファイル tsOut に書き込みます。
  • ファイルのクローズと完了通知: 全ての処理が完了したら、開いた入力ファイルと出力ファイルを確実に閉じ、ユーザーに処理完了のメッセージと保存先を表示します。

マクロの実行方法

以下の手順で、作成したマクロをExcelで実行できます。

  1. Excelを開き、Alt + F11 キーを押してVBAエディタ(Microsoft Visual Basic for Applications)を開きます。
  2. VBAエディタの左側にある「プロジェクトエクスプローラー」ウィンドウで、現在開いているブックの名前(例: VBAProject(Book1))を右クリックします。
  3. メニューから「挿入」→「標準モジュール」を選択します。
  4. 新しく開いたモジュールウィンドウに、上記のVBAコードをコピー&ペーストします。
  5. VBAエディタを閉じ、Excelワークシートに戻ります。
  6. Alt + F8 キーを押して「マクロ」ダイアログボックスを開きます。
  7. マクロ名の一覧から ConvertCsvColumnOrder を選択し、「実行」ボタンをクリックします。
  8. 表示されるファイル選択ダイアログの指示に従って、変換元のCSVファイルと変換後のCSVファイルの保存先を指定してください。
  9. 処理が完了すると、「CSVファイルの列順変換が完了しました!」というメッセージが表示されます。

注意点と応用例

エラーハンドリング

今回紹介したコードは実用性を重視していますが、より堅牢なシステムとして運用するには、以下のようなエラーハンドリングを強化すると良いでしょう。

  • CSVファイル内のデータにカンマが含まれる場合(ダブルクォーテーションで囲まれていない場合)の対応。
  • ファイルの読み書き権限がない場合や、ファイルがロックされている場合。
  • 指定したヘッダー名が複数存在する場合の対応。
  • データ行の列数がヘッダーと合わない場合の対応。
  • 空のCSVファイルや不正な形式のCSVファイルが入力された場合の対応。

応用例

このマクロをベースに、さらに以下のような機能を追加することで、より高度なデータ加工ツールとして活用できます。

  • 特定の列のデータを加工する(例: 日付形式の変換、数値の丸め、文字列の結合)。
  • 不要な列を削除するだけでなく、新しい列を追加する。
  • 複数のCSVファイルを一括で処理したり、結合したりする。
  • 特定の条件でデータをフィルタリングする(例: 特定のカテゴリの商品のみを抽出)。
  • GUI(ユーザーフォーム)を作成し、変換設定をより直感的に行えるようにする。

まとめ

Excel VBAを活用することで、CSVファイルの列順変更という、一見単純ながらも手間のかかる定型作業を効率的に自動化できます。今回紹介したマクロは、ヘッダー名を指定することで柔軟に列順を制御できるため、様々なシステム連携やデータ加工のシーンで応用可能です。

このマクロを現場に導入することで、手作業によるミスを減らし、日々のデータ処理業務の生産性を大幅に向上させることができるでしょう。ぜひこのVBAコードを参考に、ご自身の業務に合わせたカスタマイズを試してみてください。

コメント

タイトルとURLをコピーしました