はじめに:「平均算出」は“感覚をつかみやすい数字に整える技”
合計は「全体の大きさ」を教えてくれますが、
平均は「1件あたりどれくらいか?」という“感覚”を教えてくれます。
平均単価
平均購入金額
平均対応時間
こういう数字は、業務の“重さ”や“効率”を測るうえで、とても重要です。
C# / LINQ では Average を使うことで、for 文を書かずに平均を出せます。
ここから、初心者向けに
Averageの基本- オブジェクトのプロパティ平均
- null や 0 件との付き合い方
- GroupBy と組み合わせた「○○ごとの平均」
を順番にかみ砕いて説明していきます。
基本:数値コレクションに対する Average
int の平均を出す
まずは一番シンプルな例です。
using System;
using System.Collections.Generic;
using System.Linq;
var numbers = new List<int> { 1, 2, 3, 4, 5 };
double avg = numbers.Average();
Console.WriteLine(avg); // 3
C#Average() は、「合計 ÷ 件数」を内部でやってくれるメソッドです。
戻り値が double になっていることに注目してください。
ここでの重要ポイントは、「整数のリストでも、平均は小数(double)で返ってくる」ということです。(1 + 2 + 3 + 4 + 5) / 5 = 3 なので今回は整数ですが、{ 1, 2 } なら平均は 1.5 になります。
var nums = new List<int> { 1, 2 };
double avg = nums.Average(); // 1.5
C#double や decimal の平均
double や decimal のリストに対しても、同じように使えます。
var values = new List<double> { 1.2, 3.4, 5.6 };
double avg = values.Average();
C#decimal の場合は、戻り値も decimal になります。
var prices = new List<decimal> { 100.5m, 200.0m, 300.5m };
decimal avgPrice = prices.Average();
C#金額など、誤差を抑えたい場合は decimal を使うのが定番です。
オブジェクトのプロパティ平均:Average(selector)
売上明細の「平均単価」を出す
業務では、単純な数値のリストよりも、
「オブジェクトの中の特定のプロパティの平均」を出したいことが多いです。
売上明細クラスを用意します。
public class Sale
{
public string Item { get; set; } = "";
public int Quantity { get; set; }
public int UnitPrice { get; set; } // 単価
}
C#データを作ります。
var sales = new List<Sale>
{
new Sale { Item = "A", Quantity = 2, UnitPrice = 1000 },
new Sale { Item = "B", Quantity = 1, UnitPrice = 1500 },
new Sale { Item = "C", Quantity = 3, UnitPrice = 2000 },
};
C#平均単価を出します。
double avgUnitPrice = sales.Average(s => s.UnitPrice);
Console.WriteLine(avgUnitPrice); // 1500
C#ここでの重要ポイントは、「Average(s => s.UnitPrice) の中で“何を平均したいか”を指定している」ことです。Average() は「そのまま平均」、Average(x => x.プロパティ) は「プロパティを取り出してから平均」というイメージです。
計算式をその場で書いて平均する
例えば、「1件あたりの金額(数量 × 単価)の平均」を出したい場合は、こう書けます。
double avgAmountPerSale = sales.Average(s => s.Quantity * s.UnitPrice);
C#「各行の金額」をその場で計算し、それらの平均を取っています。
0 件・null との付き合い方
空コレクションに対する Average は例外になる
Sum や Count と違って、Average は「空のコレクション」に対して呼ぶと例外になります。
var empty = new List<int>();
double avg = empty.Average(); // InvalidOperationException
C#なぜかというと、「合計 ÷ 件数」で件数が 0 だと、数学的に定義できないからです。
業務的にも、「0 件の平均」は意味が曖昧ですよね。
ここでの重要ポイントは、「平均を取る前に“件数が 0 かどうか”を確認する」ことです。
double? avg = null;
if (numbers.Any())
{
avg = numbers.Average();
}
C#「データがあるときだけ平均を計算し、ないときは null にしておく」という設計がよく使われます。
null かもしれないコレクション
コレクション自体が null のこともあります。
List<int>? numbers = GetNumbersOrNull();
C#この場合も、いきなり numbers.Average() と書くと例外になります。
double? avg = null;
if (numbers != null && numbers.Any())
{
avg = numbers.Average();
}
C#あるいは、拡張メソッドとして「安全な平均」を用意してもよいです。
public static class AverageExtensions
{
public static double? SafeAverage(this IEnumerable<int>? source)
{
if (source == null) return null;
var list = source as ICollection<int> ?? source.ToList();
return list.Count == 0 ? (double?)null : list.Average();
}
}
C#使い方はこうです。
double? avg = numbers.SafeAverage();
C#ここでの重要ポイントは、「平均は“0 件のときどうするか”を必ず設計に含める」ことです。
0 件を 0 とみなすのか、null とみなすのかは、業務ルール次第です。
null 許容型(int? など)の平均
int? の平均も取れます。
public class WorkLog
{
public string User { get; set; } = "";
public int? Hours { get; set; } // null のこともある
}
C#var logs = new List<WorkLog>
{
new WorkLog { User = "A", Hours = 5 },
new WorkLog { User = "B", Hours = null },
new WorkLog { User = "C", Hours = 3 },
};
double? avgHours = logs.Average(x => x.Hours);
Console.WriteLine(avgHours); // 4
C#null は無視され、5 と 3 の平均で 4 になります。
全件 null の場合は、結果も null です。
ここでの重要ポイントは、「int? の Average は戻り値も double? になる」ことです。
「平均が存在しない」状態を null で表現できます。
GroupBy と組み合わせて「○○ごとの平均」を出す
顧客ごとの平均購入金額
グルーピングと平均は、業務集計でよく組み合わされます。
public class Order
{
public string Customer { get; set; } = "";
public int Amount { get; set; }
}
C#var orders = new List<Order>
{
new Order { Customer = "A", Amount = 1000 },
new Order { Customer = "A", Amount = 3000 },
new Order { Customer = "B", Amount = 2000 },
};
C#顧客ごとの平均購入金額を出します。
var avgByCustomer = orders
.GroupBy(o => o.Customer)
.Select(g => new
{
Customer = g.Key,
Average = g.Average(x => x.Amount)
});
foreach (var x in avgByCustomer)
{
Console.WriteLine($"{x.Customer}: 平均 {x.Average} 円");
}
C#ここでの重要ポイントは、「g.Average(...) は“そのグループの中だけ”の平均を計算している」ことです。
GroupBy で「かたまり」を作り、その中で Average を使う、という流れです。
日付ごとの平均金額
日付単位の平均もよくあります。
public class SaleWithDate
{
public DateTime Date { get; set; }
public int Amount { get; set; }
}
C#var dailyAvg = salesWithDate
.GroupBy(s => s.Date.Date)
.Select(g => new
{
Date = g.Key,
Average = g.Average(x => x.Amount)
})
.OrderBy(x => x.Date);
C#「日付ごとの平均金額」を出し、日付順に並べています。
実務で意識してほしいこと
「何の平均か」が分かる名前にする
avg だけだと、「何の平均?」が分かりません。
avgAmount, avgUnitPrice, avgHours, avgPerCustomer のように、
「何を平均した結果なのか」が分かる名前をつけてください。
double avgUnitPrice = sales.Average(s => s.UnitPrice);
double avgAmountPerOrder = orders.Average(o => o.Amount);
C#これだけで、コードの意図がかなり読みやすくなります。
平均は「外れ値」に敏感だと意識する
平均は、極端に大きい値・小さい値に引っ張られます。
例えば、
1, 2, 3, 1000 の平均は 251.5 ですが、
「ほとんどは 1〜3 なのに、平均だけ見ると 251.5」という、
直感とズレた数字になります。
実務では、
- 平均だけでなく、件数や合計も一緒に見る
- 場合によっては中央値(Median)も検討する
といった視点も大事になります(中央値は LINQ に標準ではないので、自作が必要です)。
まとめ:「平均算出ユーティリティ」は“業務の感覚を数字にするレンズ”
平均算出の本質は、
「バラバラな明細を、“1件あたりどれくらいか”という感覚的な数字に変換する」ことです。
押さえておきたいポイントは、
数値コレクションは Average()、オブジェクトは Average(x => x.プロパティ)
空コレクションに対する Average は例外になるので、Any() などで 0 件をチェックする
null 許容型の平均は null を無視し、戻り値も null になり得る
「○○ごとの平均」は GroupBy → g.Average(...) の流れで書く
変数名で「何の平均か」をはっきりさせる
このあたりが自然に書けるようになると、
「とりあえず合計だけ出している」状態から一歩進んで、
“業務の状態をより立体的に理解できる平均値”を、C# と LINQ でスマートに扱えるようになります。
