[備忘録] PowerShell データ変換パイプライン CSV変換

PowerShellのパイプラインを活用して、CSVデータの特定列だけを抽出・加工する方法を一気通貫で解説します。

日常業務でよく使うデータ変換テクニックを備忘録としてまとめました。ユースケースを想定して、コマンドを組み合わせデータ変換することにフォーカスしてみました。

![powershell-pipeline-svg.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/d443fc9f-38fa-4429-afc1-614842c1f4bc.png)


## 想定読者
- PowerShellの基本コマンドを理解している方
- CSV/Excel処理を効率化したい方
- データ変換・加工のテクニックを習得したい方

## 環境準備

この記事のサンプルはPowerShell 5.1以上で動作確認しています。まずは環境を確認しましょう。

```powershell
# バージョン確認
$PSVersionTable.PSVersion

# 作業用フォルダを作成（存在しない場合）
$workDir = "C:\Temp\PowerShellCSVDemo"
if (-not (Test-Path $workDir)) {
    New-Item -Path $workDir -ItemType Directory
}
Set-Location $workDir
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/cc3e5472-0233-40f6-96c6-0c94af1c5ef2.png)


## 実践！CSV操作パイプライン

### STEP 1: サンプルCSVファイルの作成

まずは分析対象のサンプルCSVファイルを作成します。今回は架空の「文房具店の商品データ」を例にします。

```powershell
# サンプルデータの作成
$csvData = @"
商品ID,商品名,カテゴリ,入荷日,価格,在庫数
P001,カラフルノート,ノート,2023-04-01,320,45
P002,シャープペン0.5mm,筆記具,2023-07-15,350,30
P003,メモ帳A6,ノート,2023-10-01,180,68
P004,蛍光マーカー5色セット,マーカー,2024-01-10,510,25
P005,消しゴム,文房具,2023-05-20,120,80
P006,定規15cm,文房具,2024-04-01,290,35
P007,ボールペン赤,筆記具,2023-09-01,160,55
P008,付箋紙カラフル,事務用品,2023-11-15,390,42
P009,修正テープ,文房具,2024-03-01,280,60
P010,クリップセット,事務用品,2023-06-10,200,75
"@

# CSVファイルとして保存
$csvData | Out-File -FilePath .\stationery_data.csv -Encoding utf8

# 作成したCSVファイルの内容を確認
Write-Host "作成したCSVファイルの内容:" -ForegroundColor Green
Get-Content .\stationery_data.csv
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/fd14c5c4-0113-4b8a-96b6-82752c2cbd13.png)

### STEP 2: CSVファイルの読み込みと特定列の抽出

CSVファイルを読み込み、必要な列だけを抽出します。

```powershell
# CSVファイルの読み込み
$products = Import-Csv -Path .\stationery_data.csv

# 特定の列だけを抽出する
Write-Host "`n商品の基本情報だけを抽出する:" -ForegroundColor Green
$products | Select-Object 商品ID, 商品名, カテゴリ | Format-Table

# 条件でフィルタリングする（ノートカテゴリの商品のみ）
Write-Host "`nノートカテゴリの商品だけを抽出する:" -ForegroundColor Green
$products | Where-Object { $_.カテゴリ -eq "ノート" } | Format-Table
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/89829215-6640-4568-a6d4-6bf890b3980c.png)

### STEP 3: 計算列の追加とデータ加工

既存のデータから新しい計算列を作成し、データを加工します。

```powershell
# 販売価格と在庫金額を計算
Write-Host "`n計算列を追加する:" -ForegroundColor Green
$products | 
    Select-Object 商品ID, 商品名, 価格, 
        @{Name = '税込価格'; Expression = { [int]$_.価格 * 1.1 }},
        @{Name = '在庫数'; Expression = { [int]$_.在庫数 }},
        @{Name = '在庫金額'; Expression = { [int]$_.価格 * [int]$_.在庫数 }} | 
    Format-Table

# 列名を変更する（日本語→英語）
Write-Host "`n列名を変更する:" -ForegroundColor Green
$products | 
    Select-Object @{Name = 'ProductID'; Expression = { $_.商品ID }},
                  @{Name = 'ProductName'; Expression = { $_.商品名 }},
                  @{Name = 'Category'; Expression = { $_.カテゴリ }},
                  @{Name = 'Price'; Expression = { $_.価格 }} |
    Format-Table
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/6d7e0711-c59e-45ae-82ca-b8986ddd13b8.png)

### STEP 4: データの並べ替えとグループ化

データを特定の列で並べ替えたり、グループ化して集計したりします。

```powershell
# 価格の高い順に並べ替え
Write-Host "`n価格の高い順に並べ替え:" -ForegroundColor Green
$products | 
    Sort-Object -Property { [int]$_.価格 } -Descending | 
    Select-Object 商品ID, 商品名, カテゴリ, 価格 |
    Format-Table

# カテゴリごとに集計
Write-Host "`nカテゴリ別の平均価格と在庫数:" -ForegroundColor Green
$products | 
    Group-Object -Property カテゴリ | 
    ForEach-Object {
        [PSCustomObject]@{
            カテゴリ = $_.Name
            商品数 = $_.Count
            平均価格 = ($_.Group | Measure-Object -Property 価格 -Average).Average
            合計在庫数 = ($_.Group | Measure-Object -Property 在庫数 -Sum).Sum
        }
    } | 
    Format-Table
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/b81bbfdc-4a36-423e-a40a-952541b6a412.png)

### STEP 5: 条件付き書式と高度なデータ変換

値に応じた処理や、日付データの活用方法を紹介します。

```powershell
# 価格帯に応じて価格帯ランクを設定
Write-Host "`n価格帯ランクの設定:" -ForegroundColor Green
$products | 
    Select-Object 商品ID, 商品名, 価格, 
        @{
            Name = '価格帯'; 
            Expression = { 
                $price = [int]$_.価格
                switch ($price) {
                    { $_ -ge 400 } { 'プレミアム' }
                    { $_ -ge 300 -and $_ -lt 400 } { '高価格' }
                    { $_ -ge 200 -and $_ -lt 300 } { '中価格' }
                    default { 'お手頃' }
                }
            }
        } | 
    Format-Table

# 入荷日から経過日数を計算
Write-Host "`n商品の経過日数計算:" -ForegroundColor Green
$currentDate = Get-Date
$products | 
    Select-Object 商品ID, 商品名, 入荷日, 
        @{
            Name = '経過日数'; 
            Expression = { 
                $stockDate = [DateTime]::ParseExact($_.入荷日, "yyyy-MM-dd", $null)
                [math]::Floor(($currentDate - $stockDate).TotalDays)
            }
        } | 
    Format-Table
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/c0961c3f-9b16-4920-b3e3-9021fcd7e87a.png)

### STEP 6: 実用的な関数化

これまでの処理を再利用可能な関数にまとめて、実務で活用しやすくします。

```powershell
# 商品データ分析用の汎用関数
function Convert-ToEnhancedProductData {
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [PSCustomObject]$Product,
        
        [Parameter(Mandatory = $false)]
        [switch]$IncludePriceAnalysis,
        
        [Parameter(Mandatory = $false)]
        [switch]$IncludeStockAge,
        
        [Parameter(Mandatory = $false)]
        [decimal]$TaxRate = 0.1
    )
    
    process {
        # 基本プロパティを設定
        $result = [ordered]@{
            商品ID = $Product.商品ID
            商品名 = $Product.商品名
            カテゴリ = $Product.カテゴリ
            在庫数 = [int]$Product.在庫数
        }
        
        # 価格分析を含める場合
        if ($IncludePriceAnalysis) {
            $price = [int]$Product.価格
            $result['価格'] = $price
            $result['税込価格'] = [int]($price * (1 + $TaxRate))
            $result['在庫金額'] = $price * [int]$Product.在庫数
            
            # 価格帯を追加
            $result['価格帯'] = switch ($price) {
                { $_ -ge 400 } { 'プレミアム' }
                { $_ -ge 300 -and $_ -lt 400 } { '高価格' }
                { $_ -ge 200 -and $_ -lt 300 } { '中価格' }
                default { 'お手頃' }
            }
        }
        
        # 在庫経過日数を含める場合
        if ($IncludeStockAge) {
            $stockDate = [DateTime]::ParseExact($Product.入荷日, "yyyy-MM-dd", $null)
            $currentDate = Get-Date
            $result['入荷日'] = $Product.入荷日
            $result['経過日数'] = [math]::Floor(($currentDate - $stockDate).TotalDays)
            
            # 在庫ステータスを追加
            $result['在庫ステータス'] = switch ([math]::Floor(($currentDate - $stockDate).TotalDays)) {
                { $_ -ge 180 } { '長期在庫' }
                { $_ -ge 90 -and $_ -lt 180 } { '通常在庫' }
                default { '新着商品' }
            }
        }
        
        return [PSCustomObject]$result
    }
}

# 関数の使用例：価格分析と在庫経過日数を含めたレポート
Write-Host "`n関数を使った高度な商品分析レポート:" -ForegroundColor Green
$products | 
    Convert-ToEnhancedProductData -IncludePriceAnalysis -IncludeStockAge | 
    Format-Table

# 特定カテゴリの商品だけを抽出して処理
Write-Host "`n筆記具カテゴリのみの詳細レポート:" -ForegroundColor Green
$products | 
    Where-Object { $_.カテゴリ -eq "筆記具" } | 
    Convert-ToEnhancedProductData -IncludePriceAnalysis | 
    Format-Table
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/046a4ac5-ec2a-414a-a320-e1801a9b563a.png)

### 応用例: 在庫管理レポートの作成

最後に、データを分析して在庫管理レポートを作成する例を紹介します。

```powershell
# 在庫状況のHTMLレポートを作成
$stockReport = $products | 
    ForEach-Object {
        $stockDate = [DateTime]::ParseExact($_.入荷日, "yyyy-MM-dd", $null)
        $currentDate = Get-Date
        $daysSinceArrival = [math]::Floor(($currentDate - $stockDate).TotalDays)
        
        $stockStatus = switch ($daysSinceArrival) {
            { $_ -ge 180 } { '長期在庫' }
            { $_ -ge 90 -and $_ -lt 180 } { '通常在庫' }
            default { '新着商品' }
        }
        
        $inventoryValue = [int]$_.価格 * [int]$_.在庫数
        
        [PSCustomObject]@{
            商品ID = $_.商品ID
            商品名 = $_.商品名
            カテゴリ = $_.カテゴリ
            入荷日 = $_.入荷日
            経過日数 = $daysSinceArrival
            在庫ステータス = $stockStatus
            在庫数 = [int]$_.在庫数
            在庫金額 = $inventoryValue
        }
    } | 
    Sort-Object -Property 在庫ステータス, カテゴリ |
    ConvertTo-Html -Title "在庫管理レポート" -PreContent "<h1>文房具店 在庫管理レポート</h1>" -PostContent "<p>生成日時: $(Get-Date)</p>"

$stockReport | Out-File -FilePath .\inventory_report.html -Encoding utf8

Write-Host "`nHTMLレポートを生成しました: $PWD\inventory_report.html" -ForegroundColor Green

# 在庫金額の集計
Write-Host "`n在庫金額の集計:" -ForegroundColor Green
$totalValue = ($products | 
    ForEach-Object { [int]$_.価格 * [int]$_.在庫数 } | 
    Measure-Object -Sum).Sum

Write-Host "店舗の総在庫金額: $totalValue 円" -ForegroundColor Yellow

# カテゴリごとの在庫金額の割合
$products | 
    Group-Object -Property カテゴリ | 
    ForEach-Object {
        $categoryTotal = ($_.Group | 
            ForEach-Object { [int]$_.価格 * [int]$_.在庫数 } | 
            Measure-Object -Sum).Sum
            
        [PSCustomObject]@{
            カテゴリ = $_.Name
            在庫金額 = $categoryTotal
            割合 = [math]::Round(($categoryTotal / $totalValue) * 100, 1)
        }
    } | 
    Sort-Object -Property 在庫金額 -Descending | 
    Format-Table
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/1eb1bb72-4406-4cd1-8113-6abe048938d8.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/4e75171a-2fee-4bf8-b810-378da0960b68.png)

## まとめ

![powershell-summary-svg.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/cdf8661b-2853-47cb-baef-a32bc03ff425.png)

PowerShellのパイプラインとSelect-Objectを活用することで、CSVデータの特定列を効率よく抽出・加工できることが分かりました。実務では、不要な列の除外や必要な情報への絞り込み、列名の変更や計算列の追加といった変換処理、値に応じた条件付きの出力、さらには集計やグループ化によるレポート作成など、さまざまな場面で活用できます。

## 参考資料
- [Microsoft PowerShell Documentation](https://docs.microsoft.com/en-us/powershell/)
- [PowerShell Gallery](https://www.powershellgallery.com/)
- [GitHub - PowerShell Community](https://github.com/powershell/powershell)

---

*この記事は2025年5月に執筆されました。PowerShellのバージョンやコマンドレットの仕様は、今後のアップデートで変更される可能性があります。*