[備忘録] PowerShellオブジェクト操作マスターガイド：ラーメン店データ分析を試す

## はじめに

PowerShellの真の強みはオブジェクト指向パイプラインにあります。
実務では複数データソースの統合や効率的なデータ変換が求められます。本記事では「ラーメン店分析」を試してみました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/48d9b25d-8678-4ad4-bf12-c9bbc5a35070.png)

## サンプルコード

以下のコードは記事で紹介する全テクニックを一括で実行可能なサンプルです。コピーしてそのまま実行できます。

```powershell
# ===== ラーメン店サンプルデータの作成 =====
$ramenShops = @(
    [PSCustomObject]@{
        Name = "豚骨伝説 福岡亭"
        City = "福岡市"
        Type = "豚骨"
        Rating = 4.5
        PriceRange = "中"
    },
    [PSCustomObject]@{
        Name = "雪国味噌らーめん"
        City = "札幌市"
        Type = "味噌"
        Rating = 4.7
        PriceRange = "中"
    },
    [PSCustomObject]@{
        Name = "和風醤油麺 匠"
        City = "東京都"
        Type = "醤油"
        Rating = 4.8
        PriceRange = "低"
    },
    [PSCustomObject]@{
        Name = "麻辣火鍋麺 四川"
        City = "大阪市"
        Type = "担々麺"
        Rating = 4.6
        PriceRange = "高"
    }
)

# 初期データの確認
Write-Host "==== 初期データ ====" -ForegroundColor Cyan
$ramenShops | Format-Table

# ===== 1. Add-Memberでプロパティを追加 =====
Write-Host "`n==== 1. Add-Memberでプロパティを追加 ====" -ForegroundColor Cyan
$ramenShops | ForEach-Object {
    $_ | Add-Member -MemberType NoteProperty -Name "EstablishedYear" -Value (Get-Random -Minimum 1980 -Maximum 2023) -Force
}

Write-Host "設立年を追加した結果:"
$ramenShops | Format-Table

# ===== 2. Select-Objectで計算プロパティを追加 =====
Write-Host "`n==== 2. Select-Objectで計算プロパティを追加 ====" -ForegroundColor Cyan
$ramenShopsExtended = $ramenShops | Select-Object *, 
    @{Name="YearsInBusiness"; Expression={(Get-Date).Year - $_.EstablishedYear}},
    @{Name="PriceCategory"; Expression={
        switch($_.PriceRange) {
            "低" {"¥ ~ ¥¥"}
            "中" {"¥¥ ~ ¥¥¥"}
            "高" {"¥¥¥¥ ~"}
        }
    }}

Write-Host "計算プロパティを追加した結果:"
$ramenShopsExtended | Format-Table

# ===== 3. 条件付きプロパティ変更 =====
Write-Host "`n==== 3. 条件付きプロパティ変更 ====" -ForegroundColor Cyan
$ramenShops | Where-Object { $_.City -eq "東京都" } | ForEach-Object {
    $_.City = "東京都 新宿区"
}

Write-Host "特定条件のデータを変更した結果:"
$ramenShops | Format-Table

# ===== 4. 複数プロパティの一括変換 =====
Write-Host "`n==== 4. 複数プロパティの一括変換 ====" -ForegroundColor Cyan
# 元データを保存
$originalRamenShops = $ramenShops | Select-Object *

# 評価を変換
$ramenShops | ForEach-Object {
    # 評価を10点満点に標準化
    $_.Rating = [math]::Round($_.Rating * 2, 1)
    
    # 店名に評価を追加
    $_.Name = "$($_.Name) [★$($_.Rating)]"
}

Write-Host "評価と店名を変換した結果:"
$ramenShops | Format-Table

# ===== 5. 外部データとの統合シミュレーション =====
Write-Host "`n==== 5. 外部データとの統合シミュレーション ====" -ForegroundColor Cyan

# レビューデータのシミュレーション作成
$reviewData = @(
    [PSCustomObject]@{ ShopName = "豚骨伝説 福岡亭"; ServiceRating = 4.2; Comment = "スタッフの対応が素晴らしい" },
    [PSCustomObject]@{ ShopName = "豚骨伝説 福岡亭"; ServiceRating = 3.8; Comment = "少し待ち時間が長い" },
    [PSCustomObject]@{ ShopName = "雪国味噌らーめん"; ServiceRating = 4.5; Comment = "接客が丁寧" },
    [PSCustomObject]@{ ShopName = "雪国味噌らーめん"; ServiceRating = 4.7; Comment = "素晴らしい対応" },
    [PSCustomObject]@{ ShopName = "雪国味噌らーめん"; ServiceRating = 4.6; Comment = "また行きたい" },
    [PSCustomObject]@{ ShopName = "和風醤油麺 匠"; ServiceRating = 4.9; Comment = "最高のサービス" },
    [PSCustomObject]@{ ShopName = "麻辣火鍋麺 四川"; ServiceRating = 4.1; Comment = "丁寧な説明があった" }
)

# テスト用にCSVファイルを作成
$reviewData | Export-Csv -Path ".\reviews.csv" -Encoding UTF8 -NoTypeInformation
Write-Host "レビューデータのCSVファイルを作成しました: .\reviews.csv"

# CSVからデータを読み込む
$reviewDataFromCsv = Import-Csv ".\reviews.csv"

# データ統合と変換を一度に実行
$integratedData = $originalRamenShops | ForEach-Object {
    $shop = $_
    $reviews = $reviewDataFromCsv | Where-Object { $_.ShopName -eq $shop.Name }
    
    # 統計情報を追加
    $shop | Add-Member -MemberType NoteProperty -Name "ReviewCount" -Value ($reviews.Count) -Force
    
    if ($reviews.Count -gt 0) {
        $avgServiceRating = ($reviews | Measure-Object -Property ServiceRating -Average).Average
        $shop | Add-Member -MemberType NoteProperty -Name "AvgServiceRating" -Value ([math]::Round($avgServiceRating, 1)) -Force
    } else {
        $shop | Add-Member -MemberType NoteProperty -Name "AvgServiceRating" -Value $null -Force
    }
    
    $shop # 変更したオブジェクトをパイプラインに返す
}

Write-Host "レビューデータと統合した結果:"
$integratedData | Format-Table

# 最終結果の表示
Write-Host "`n==== 最終結果 ====" -ForegroundColor Cyan
Write-Host "すべての処理が適用された後のデータ:"
$integratedData | Format-Table -AutoSize

Write-Host "`n==== 全ての処理が完了しました ====" -ForegroundColor Green
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/4fa8ccc5-552b-43d4-9564-e5fd58ee94aa.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/bd1fc6b4-f6c7-4a4d-9eb9-720c2dd0f9b1.png)


## 基本のおさらい: オブジェクトの構造

PowerShellでは、ほぼすべてがオブジェクトとして扱われます。以下はラーメン店データの基本構造です：

```powershell
$ramenShops = @(
    [PSCustomObject]@{
        Name = "豚骨伝説 福岡亭"
        City = "福岡市"
        Type = "豚骨"
        Rating = 4.5
        PriceRange = "中"
    },
    # 他の店舗データ...
)
```

## オブジェクト操作の実践テクニック

### 1. 実務に役立つプロパティ追加

**Add-Member：単一プロパティの追加（用途：外部APIからのデータ拡張）**

```powershell
# 設立年を追加（実務では外部データベースやAPIから取得）
$ramenShops | ForEach-Object {
    $_ | Add-Member -MemberType NoteProperty -Name "EstablishedYear" -Value (Get-Random -Minimum 1980 -Maximum 2023)
}
```

**Select-Object：計算プロパティの一括追加（用途：レポート作成）**

```powershell
# より表現力豊かなデータに変換
$ramenShopsExtended = $ramenShops | Select-Object *, 
    @{Name="YearsInBusiness"; Expression={(Get-Date).Year - $_.EstablishedYear}},
    @{Name="PriceCategory"; Expression={
        switch($_.PriceRange) {
            "低" {"¥ ~ ¥¥"}
            "中" {"¥¥ ~ ¥¥¥"}
            "高" {"¥¥¥¥ ~"}
        }
    }}
```

### 2. 実践的なデータ変換テクニック

**条件付きプロパティ変更（用途：データクレンジング）**

```powershell
# 東京都の店舗情報を詳細化（データ正規化の例）
$ramenShops | Where-Object { $_.City -eq "東京都" } | ForEach-Object {
    $_.City = "東京都 新宿区"
}
```

**複数プロパティの一括変換（用途：データ標準化）**

```powershell
# 評価システムを変換し、店名にも反映
$ramenShops | ForEach-Object {
    # 評価を10点満点に標準化
    $_.Rating = [math]::Round($_.Rating * 2, 1)
    
    # 店名に評価を追加（顧客向けデータ表示用）
    $_.Name = "$($_.Name) [★$($_.Rating)]"
}
```

### 3. 実務で重要なデータソース統合

**CSV・APIデータの取り込みと変換（実務ユースケース）**

```powershell
# CSVからデータを読み込む例
$reviewData = Import-Csv ".\reviews.csv"

# データ統合と変換を一度に実行
$integratedData = $ramenShops | ForEach-Object {
    $shop = $_
    $reviews = $reviewData | Where-Object { $_.ShopName -eq $shop.Name }
    
    # 統計情報を追加
    $shop | Add-Member -MemberType NoteProperty -Name "ReviewCount" -Value ($reviews.Count)
    $shop | Add-Member -MemberType NoteProperty -Name "AvgServiceRating" -Value ($reviews | Measure-Object -Property ServiceRating -Average).Average
    
    $shop # 変更したオブジェクトをパイプラインに返す
}
```

- 大量データには foreach を使うと高速
- フィルタ処理は早めに行い、無駄なデータを減らす
- 複雑な変換は段階的に行い、都度結果を確認

## まとめ

![powershell-summary.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/1a62ea75-6686-41f5-a466-9477fd4f3b4d.png)

PowerShellオブジェクトの基本を押さえれば複雑なデータ処理も簡単になります。実務では、プロパティを計画的に設計し、変換作業は小さく分けて進め、データの不整合に備えたエラー対策を忘れないことが大切です。これらのテクニックで、効率的で堅牢なデータ処理を実現しましょう。

## 参考リンク

- [about_Properties (Microsoft Docs)](https://docs.microsoft.com/ja-jp/powershell/module/microsoft.powershell.core/about/about_properties)
- [Add-Member (Microsoft Docs)](https://docs.microsoft.com/ja-jp/powershell/module/microsoft.powershell.utility/add-member)
- [PowerShellのパフォーマンス最適化](https://docs.microsoft.com/ja-jp/powershell/scripting/dev-cross-plat/performance/script-authoring-considerations)