【C#】座標変換と最小二乗法 〜データ分析の数理とその実装〜

## はじめに

データ分析や画像処理において、座標変換や近似計算は頻繁に必要となる処理です。本記事では、C#を使用して以下の処理を実装する方法を紹介します。

1. 回転行列による座標変換
2. 最小二乗法による近似直線の算出
3. データの正規化と評価指標の計算

実装を通じて、数学的な概念がどのようにプログラムに変換されるのかを理解していきましょう。

3次元空間での回転についてはこちらも参考にしてください。

https://qiita.com/Sakai_path/items/d7231aa6a3d92ade9e86


## 理論的背景

### 回転行列による座標変換

2次元平面上の点(x, y)を角度θだけ回転させる場合、以下の回転行列を使用します。

```math
\begin{pmatrix} 
x' \\ 
y'
\end{pmatrix} = 
\begin{pmatrix} 
\cos \theta & -\sin \theta \\
\sin \theta & \cos \theta
\end{pmatrix}
\begin{pmatrix}
x \\
y
\end{pmatrix}
```

これは以下の計算を行うことを意味します。
- x' = x * cos(θ) - y * sin(θ)
- y' = x * sin(θ) + y * cos(θ)

### 最小二乗法

データポイントの集合に対して最適な直線を見つける場合、最小二乗法を使用します。直線の式を y = β₁x + β₀ とすると、係数は以下の式で求められます。

```math
\beta_1 = \frac{n\sum xy - \sum x \sum y}{n\sum x^2 - (\sum x)^2}
```

```math
\beta_0 = \frac{\sum y - \beta_1 \sum x}{n}
```

ここで
- β₁: 直線の傾き
- β₀: y切片
- n: データポイントの数

## 実装

### 座標変換の実装

まず、回転行列による座標変換を実装します。

```csharp:C#
public static (double X, double Y) RotatePoint(double x, double y, double angleInDegrees)
{
    double angleInRadians = angleInDegrees * Math.PI / 180;
    double cosTheta = Math.Cos(angleInRadians);
    double sinTheta = Math.Sin(angleInRadians);
    
    return (
        X: x * cosTheta - y * sinTheta,
        Y: x * sinTheta + y * cosTheta
    );
}
```

このメソッドの特徴
- タプルを使用して複数の戻り値を返す
- 度数法での入力を弧度法に変換
- 三角関数の計算を1回だけ行い、結果を再利用

### 最小二乗法の実装

次に、最小二乗法による近似直線の計算を実装します。

```csharp:C#
public static (double Slope, double Intercept) CalculateLinearRegression(
    double[] xValues, double[] yValues)
{
    if (xValues.Length != yValues.Length)
        throw new ArgumentException("配列の長さが一致しません");

    int n = xValues.Length;
    double sumX = xValues.Sum();
    double sumY = yValues.Sum();
    double sumXY = xValues.Zip(yValues, (x, y) => x * y).Sum();
    double sumXX = xValues.Select(x => x * x).Sum();

    double slope = (n * sumXY - sumX * sumY) / (n * sumXX - sumX * sumX);
    double intercept = (sumY - slope * sumX) / n;

    return (Slope: slope, Intercept: intercept);
}
```

:::note info
**実装のポイント**
- LINQ を使用して配列の集計を簡潔に記述
- 入力値の検証を適切に行う
- 名前付きタプルで戻り値の意味を明確に
:::

### データの正規化処理

データの特性を把握しやすくするため、正規化処理を実装します。

```csharp:C#
public static double[] NormalizeData(double[] values)
{
    double min = values.Min();
    double max = values.Max();
    return values.Select(v => (v - min) / (max - min)).ToArray();
}
```

### モデルの評価

最後に、モデルの精度を評価するためのR²値を計算します。

```csharp:C#
public static double CalculateRSquared(double[] observed, double[] predicted)
{
    double meanObserved = observed.Average();
    double ssTotal = observed.Select(y => Math.Pow(y - meanObserved, 2)).Sum();
    double ssResidual = observed.Zip(predicted, (o, p) => Math.Pow(o - p, 2)).Sum();
    
    return 1 - (ssResidual / ssTotal);
}
```

## 使用例

実際の使用例を見てみましょう。

```csharp:C#
// サンプルデータの生成
var points = new[]
{
    (X: 1.0, Y: 2.0),
    (X: 2.0, Y: 4.0),
    (X: 3.0, Y: 5.0),
    (X: 4.0, Y: 4.0),
    (X: 5.0, Y: 5.0)
};

// 1. 全ての点を45度回転
var rotatedPoints = points.Select(p => 
    CoordinateCalculator.RotatePoint(p.X, p.Y, 45)).ToArray();

// 2. 回転後の座標で近似直線を計算
var xValues = rotatedPoints.Select(p => p.X).ToArray();
var yValues = rotatedPoints.Select(p => p.Y).ToArray();

var (slope, intercept) = CoordinateCalculator.CalculateLinearRegression(xValues, yValues);

// 3. 予測値の計算と評価
var predictedY = xValues.Select(x => slope * x + intercept).ToArray();
double rSquared = CoordinateCalculator.CalculateRSquared(yValues, predictedY);
```

## パフォーマンスと注意点

### 精度に関する注意

1. 浮動小数点数の計算における誤差
   - `double` 型を使用する際は、丸め誤差の蓄積に注意
   - 金額計算など高精度が必要な場合は `decimal` 型を検討

2. 座標変換での注意点
   - 大きな角度の回転を行う場合、誤差が大きくなる可能性
   - 必要に応じて正規化処理を実施

### メモリ使用量の最適化

1. LINQ の使用
   - `Select` や `Where` は遅延評価
   - 必要に応じて `ToArray()` や `ToList()` を使用

2. 大規模データセットでの考慮点
   - メモリ内に収まらないデータの場合、ストリーム処理を検討
   - 並列処理の活用（ただし、オーバーヘッドに注意）

3. 計算処理の高速化

大規模データセットを扱う場合、SIMD（Single Instruction, Multiple Data）演算を活用することで計算速度を向上できます。C#では`System.Numerics.Vector<T>`クラスを使用して実装します。

```csharp:C#
using System.Numerics;

public static void RotatePointsSimd(double[] xValues, double[] yValues, 
    double angle, double[] resultX, double[] resultY)
{
    double radians = angle * Math.PI / 180;
    var cosTheta = new Vector<double>(Math.Cos(radians));
    var sinTheta = new Vector<double>(Math.Sin(radians));
    
    int simdLength = Vector<double>.Count;
    for (int i = 0; i <= xValues.Length - simdLength; i += simdLength)
    {
        var xVector = new Vector<double>(xValues, i);
        var yVector = new Vector<double>(yValues, i);

        (xVector * cosTheta - yVector * sinTheta).CopyTo(resultX, i);
        (xVector * sinTheta + yVector * cosTheta).CopyTo(resultY, i);
    }
}
```

## まとめ

本記事では、C#を使用して座標変換と最小二乗法の実装を行いました。実装のポイントは・・

1. 数学的な理論の理解
2. 適切なデータ構造の選択
3. パフォーマンスと精度のバランス
4. エラー処理と入力値の検証

これらの実装は、データ分析や画像処理など様々な分野で活用できます。

## 参考資料

### Microsoft公式ドキュメント
- [C# によるタプルの使用](https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/builtin-types/value-tuples)
- [LINQ の使用](https://learn.microsoft.com/ja-jp/dotnet/csharp/programming-guide/concepts/linq/)
- [浮動小数点数型（C# リファレンス）](https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/builtin-types/floating-point-numeric-types)
- [SIMD アクセラレータの数値型を使用する](https://learn.microsoft.com/ja-jp/dotnet/standard/simd)