【C言語】合計・平均・分散・標準偏差を求める

C言語での合計・平均・分散・標準偏差の求め方の解説ページアイキャッチ

このページでは、基本的な統計的指標である下記の4つをC言語で求める方法について解説していきます。

  • 合計
  • 平均(算術平均)
  • 分散
  • 標準偏差

残念ながら、これらを一発で求めるような標準ライブラリ関数はC言語では用意されていません。そのため、これらを求めたい時には、自身でプログラミングを行なって結果を計算する必要があります。

ただ、単に式に当てはめて計算すれば良いだけなので、基本的に行うことは単純だと思います。

それでもC言語でプログラミングしてこれらを求める際には注意点もありますし、実際に実装してみて得られる気づきもあると思いますので、その辺りも随時補足しながら、C言語での合計・平均(算術平均)・分散・標準偏差の求め方を解説していきたいと思います。

また、今回は与えられたデータに対して平均や分散などを単純に計算する方法についてのみ解説を行います。

例えば母集団から無作為に取得した標本データの平均や分散から母集団の平均や分散などを推定するようなことも可能ですが、今回はそこまでは解説せず、単に平均や分散を求めるだけの解説となりますのでご了承ください。

データの扱い方

特にC言語入門者の方に向けて、最初は凄く基礎的な話から解説していきたいと思います。

要は「複数のデータを扱う際には配列を使おう」という話ですので、この点を理解されている方は次の 合計 までスキップしていただければと思います。

まず、平均や分散などといった今回求め方を解説していく統計的指標は複数のデータから求めるのが一般的です。

なので、これらを求める際にはC言語のプログラムの中で複数のデータを扱う必要があります。

例えば生徒五人のテストの平均点や分散等の統計的指標を求める際には5つのデータを扱う必要があります。

統計的指標値を求めるために複数のデータを扱う様子

C言語では基礎的な「複数のデータの扱い方」には大きく分けて3つが存在します。各データを prinf 関数で表示する例を用いて、各データの扱い方について確認していきましょう!

データを直接扱う

1つ目のデータの扱い方は「データを直接扱う」になります。変数などは使用せずに、値そのものを扱います。

データを prinf で表示するのであれば、下記のように直接 prinfの引数にデータを指定して表示を行うことになります。

データを直接扱う
printf("%d\n", 95);
printf("%d\n", 57);
printf("%d\n", 88);
printf("%d\n", 49);
printf("%d\n", 71);

もっとも単純ですが、データの数値が変化してしまったような場合はプログラム自体を変更する必要があります。ソースコードを変更してからコンパイル・リンクを行なう必要があって手間が掛かります。

スポンサーリンク

データを変数で扱う

2つ目のデータの扱い方は「データを変数で扱う」になります。

各データを変数で扱う様子

各データを別々の変数に格納し、その変数を使用することで複数のデータを扱います。

データを prinf で表示するのであれば、下記のように変数を prinfの引数に指定して表示を行うことになります。

データを変数で扱う
int a, b, c, d, e;

a = 95;
b = 57;
c = 88;
d = 49;
e = 71;

printf("%d\n", a);
printf("%d\n", b);
printf("%d\n", c);
printf("%d\n", d);
printf("%d\n", e);

上記の場合は、データが変化してしまった場合は結局プログラム自体を変更する必要があります。

ですが、次のようにデータを scanf でデータを入力できるようにし、さらにそのデータを変数に格納するようにしてやれば、データの数値が変化した場合でも scanf で入力する数値を変更すれば良いだけになります。

ですので、単にデータが変化するだけであればプログラム自体の変更は不要になります。

データをscanfで入力する
int a, b, c, d, e;

scanf("%d", &a);
scanf("%d", &b);
scanf("%d", &c);
scanf("%d", &d);
scanf("%d", &e);

printf("%d\n", a);
printf("%d\n", b);
printf("%d\n", c);
printf("%d\n", d);
printf("%d\n", e);

そのため、データを直接扱う場合よりもプログラムの汎用性が上がったことになります。

ただ、それでも上記の場合はデータが5個であることを前提としたプログラムとなっているため、データの個数が変わってしまうと結局プログラムの変更が必要になってしまい手間が掛かります。

データを配列で扱う

で、これを解決するのが3つ目の「データを配列で扱う」になります。各データを配列の先頭側の要素から順々に格納し、この要素を使用することで複数のデータを扱います。

全データを配列でまとめて扱う様子

データを prinf で表示するのであれば、下記のように配列の要素を prinfの引数に指定して表示を行うことになります。

データを配列で扱う
int nums[100];

scanf("%d", &nums[0]);
scanf("%d", &nums[1]);
scanf("%d", &nums[2]);
scanf("%d", &nums[3]);
scanf("%d", &nums[4]);

printf("%d\n", nums[0]);
printf("%d\n", nums[1]);
printf("%d\n", nums[2]);
printf("%d\n", nums[3]);
printf("%d\n", nums[4]);

上記の場合はデータの個数が変わると結局プログラム自体の変更が必要になってしまうのですが、次のように for 文で繰り返し処理を行い、繰り返しの中で要素を指定する添字(下記の場合は i)を変化させて配列の先頭から順々に要素を使用するようにすれば、同じプログラムで様々な個数のデータを扱うことが可能になります。

可変なデータ個数のデータを配列で扱う
int nums[100];
int n;

printf("データの個数は?:");
scanf("%d", &n);

if (n > 100) {
    printf("%d個以上のデータは扱えません...\n", 101);
    return -1;
}

for (int i = 0; i < n; i++) {
    scanf("%d", &nums[i]);
}

for (int i = 0; i < n; i++) {
    printf("%d\n", nums[i]);
}

ただ、データの個数(上記の場合は n)はしっかり管理しておく必要がありますし、データの個数が配列の要素数(上記の場合は 100)を超える場合は上手くデータを扱うことができなくなるので注意してください。

MEMO

添字に配列の要素数以上の値を指定するとメモリアクセス違反が発生したり、メモリ破壊が発生する可能性がありますので特に注意が必要です

こういった制限はあるものの、複数のデータをまとめて扱う際には配列を利用することでソースコードがスッキリしますし、データの個数に対しても柔軟性の高いプログラムに仕立てることができます。

ここでは単にデータの表示のみを行いましたが、以降では上記のようにデータを配列で扱うことを前提として合計・平均・分散・標準偏差の求め方を解説していきます。

また、上記の解説ではデータは scanfで入力することを前提としてソースコード等を紹介しましたが、大事なのは配列を利用している点(さらには for 文を利用している点)であり、他の方法でデータを入力するように変更しても構いません。例えばデータが大量に存在するような場合はファイルから読み込んだ方が楽だと思います。

合計

それでは、ここから本題の合計・平均・分散・標準偏差の求め方について解説していきます。

まずは合計の求め方について解説します。

スポンサーリンク

合計の求め方

合計は、単純に全データの値を足し合わせることで求めることができます。

合計の求め方の説明図

データの扱い方 で解説したように、各データを配列の先頭側の要素から順に格納してやれば、単純に配列の先頭から順に各要素の値を足し合わせてやることで合計が求められます。

つまり、各データが格納された配列を nums、データの個数を n とすれば、下記の足し算を行うことで合計を求めることができることになります。

合計の算出式
nums[0] + nums[1] + ・・・ + nums[n - 2] + nums[n - 1]

合計を求める関数

このような合計値は、下記の calcSum 関数のように、for 文の中で配列の添字を変化させながら、その添字の要素を足し合わせていくことで計算することができます。for 文が終了した時点で変数 sum に格納されている値が合計ということになります。

合計を求める関数
int calcSum(int nums[], int n) {
    int sum = 0;

    for (int i = 0; i < n; i++) {
        sum = sum + nums[i];
    }

    return sum;
}

「データが整数であること」を前提として numssum、さらには返却値の型を int 型にしていますが、浮動小数点数のデータの合計を求めたいような場合は、これらの変数や返却値の型を double 等に変更してやれば良いです。

念の為、上記の for 文の動きを確認しておきましょう。

上記の for 文の1回目の繰り返しにおいては、下記の計算結果が sum に新たに格納されることになります。

1回目の繰り返し
sum の初期値 + nums[0]

2回目の繰り返しにおいては、sum + nums[1] の結果が sum に新たに格納されることになりますが、上記の式より結局 sumには下記の計算結果が格納されることになります。

2回目の繰り返し
sum の初期値 + nums[0] + nums[1]

同様に、3回目以降の繰り返しにおいても、前回計算した sum の値を用いて合計が求められることになり、最後の n 回目の繰り返しにおいては下記が新たに sum が格納されることになります。

n回目の繰り返し
sum の初期値 + nums[0] + nums[1] + ・・・ + nums[n - 2] + nums[n - 1]

そして、上記が格納された sumcalcSum 関数から返却されることになります。

合計を求める際の注意点

前述で紹介した通り、配列を nums、データの個数を n とした時のデータの合計を求める式は下記となります。

合計の算出式
nums[0] + nums[1] + ・・・ + nums[n - 2] + nums[n - 1]

それに対し、calcSum 関数の返却値は下記式から求められる値となります。

calcSumの返却値
sum の初期値 + nums[0] + nums[1] + ・・・ + nums[n - 2] + nums[n - 1]

この2つの式の差分からも分かるように、先程紹介した for 文で正しくデータの合計を求めることができるのは「sum の初期値 が 0 である場合のみ」となります。

したがって、合計を求めるための for 文を実行する前には、必ず合計の格納先となる変数(calcSum 関数の場合は sum)に 0 を格納しておく必要があります。この点に注意してください。

もし、for 文に入ったタイミングでの sum の値が 0 以外であれば、最終的に sum に格納される値は本来のデータの合計とは異なる値になってしまいます。

さらにC言語では、初期化を行わなかった場合、宣言直後の(動的)変数の値は不定値となります。たまたま変数の値が 0 であることもありますが、基本的には何が格納されているか分からない状態です。

そのため、sum0 で初期化もせず、さらに sum = 0 を行わずに for 文を実行した場合は基本的には正しく合計を求めることができません。

この for 文を実行する前に必ず sum に 0を代入しておく必要がある(もしくは sum0 で初期化しておく必要がある)という点が、データの合計を求める際の注意点の1つとなります。

また、データの合計を求める際にはオーバーフローにも注意してください。合計の格納先の変数(calcSum 関数の場合は sum)の型は、実際の合計値を扱える型である必要があります。型で扱える最大値よりも大きな値(もしくは最小値よりも小さい値)を変数に代入すると、オーバーフローが発生して上手く合計を求めることができません。

例えば sum の型である int 型で扱える値は、処理系依存ではありますが、多くの環境において下記の範囲のみとなります。

-2147483648 〜 2147483647

合計が上記よりも大きな値 or 小さな値になるような場合はオーバーフローが発生してしまうことになるため、int 型よりも扱える値の範囲が大きい型、例えば longlong long、もしくは浮動小数点数の double 型等を合計値を格納する変数の型に利用する必要があります。

特に扱うデータの値が大きい(or 小さい)場合やデータの個数が多いような場合はオーバーフローに注意が必要です。

スポンサーリンク

平均

次は平均の求め方について解説していきます。

平均の求め方

平均(算術平均)の計算は簡単で、要はデータの合計をデータの個数で割ってやれば良いだけです。

平均の求め方の説明図

ですので、まずはデータの合計を 合計の求め方 に従って求めてやり、後は求めた合計をデータの個数で割ってやれば良いだけです。

平均を求める関数

平均を求める関数の例は下記の calcAve となります。引数の nums を各データが格納された配列、n をデータの個数としています。

平均を求める関数
double calcAve(int nums[], int n) {

    if (n == 0) {
        printf("nが0なので平均が計算できません\n");
        return 0;
    }

    int sum = calcSum(nums, n);
    double ave = (double)sum / (double)n;

    return ave;
}

calcSum合計を求める関数 で紹介した関数になります。

スポンサーリンク

平均を求める際の注意点

平均の求め方自体は単純ですが、特にC言語で実装する場合は下記の2つに注意してください。

  • ゼロ除算
  • 整数同士の割り算

ゼロ除算

まず、平均を求める際には合計をデータの個数で割るのですから、データの個数が 0 の場合は 0 で除算が行われることになります。C言語のプログラムにおいては 0 除算を行うと異常終了する可能性がありプログラムは正常に動作することができません。

なので、除算(剰余算も)を行う際には除数が 0 でないことを保証した状態で実行する必要があります。言い換えれば、除数が 0 の場合は除算を行わないようにする必要があります。

そのため、平均を求める関数 で紹介した関数では、データの個数が 0 の場合は平均を求めるための除算は行わず、エラーを出力するようにしています。

整数同士の割り算

また、C言語においては整数同士の割り算結果は整数となり、小数点以下が切り捨てされてしまいます。つまり、被除数も除数も両方とも整数型の変数とする場合、小数点以下が切り捨てられてしまうことになります。

この場合、平均の値を格納する変数が浮動小数点数型(double など)であったとしても、小数点以下の値は 0 となってしまいます。

なので、平均の値として小数点以下の値も含めて計算する場合は、平均を求める際の割り算において、被除数と除数の両方が整数にならないように注意する必要があります。

特に注意が必要なのが「データの個数を格納する変数」と「データの合計を格納する変数」の両方が整数型であるケースで、この場合は単純に平均を求めると 整数型の変数 / 整数型の変数 の計算が行われることになり、小数点以下の値を求めることができません(切り捨てられて必ず 0 になる)。

そのため、平均を求める際の割り算が 整数型の変数 / 整数型の変数 になってしまうような場合は、分母 or 分子のどちらか一方、もしくは両方を浮動小数点数型に変換してから計算を行う必要があります。

こうすることで、割り算結果を浮動小数点数型として得ることができ、小数点以下の値も求めることができるようになります。

さらに、この型の変換は (型名) の形式の「キャスト演算子」を変数名の前に指定することで実現することができます。

上記のソースコードの例では、平均を求める際の割り算において分母と分子の変数両方に対して (double) を指定しているため、元々 int 型の変数 / int 型の変数 となっていた割り算を double 型の変数 / double 型の変数 として計算されるようにしており、これにより小数点以下の値も求められるようにしています。

分散

次は分散の求め方について解説していきます。

分散の求め方

分散は「各データとデータの平均との差」を2乗した値の平均として求めることができます。

分散の求め方の説明図

式で表せば、データが \( x_0 \) から \( x_{n-1} \) の \( n \) 個のデータの場合、分散 \( \sigma^2 \) は下記の式により求めることができます(\( \mu \) はデータの平均を表す)。

$$ \sigma^2 = \frac{1}{n} \sum_{i = 0}^{n-1} (x_i – \mu)^2 $$

式を見ると複雑そうですが、\( y_i = (x_i – \mu)^2 \) とすれば、要は \( y_0 \) から \( y_{n-1} \) の \( n \) 個のデータの合計を求め、さらにその合計から平均を求めてやれば良いだけです。

$$ \sigma^2 = \frac{1}{n} \sum_{i = 0}^{n-1} y_i $$

なので、まずは上式における \( \mu \) であるデータの平均値を 平均値の求め方 に従って求めてやり、さらに \( y_i = (x_i – \mu)^2 \) をデータと考えて 合計の求め方 のとおり合計を求め、その結果を  平均値の求め方 のとおりデータの個数で割ってやれば、分散を求めることができることになります。

スポンサーリンク

分散を求める関数

分散を求める関数の例は下記の calcVar となります。引数の nums を各データが格納された配列、n をデータの個数としています。

分散を求める関数
double calcVar(int nums[], int n) {

    if (n == 0) {
        printf("nが0なので分散が計算できません\n");
        return 0;
    }

    double ave = calcAve(nums, n);
    double sum;
    
    sum = 0;
    for (int i = 0; i < n; i++) {
        double y = (nums[i] - ave) * (nums[i] - ave);
        sum = sum + y;
    }

    double var = sum / n;

    return var;
}

calcAve平均を求める関数 で紹介した関数になります。

for 文の中は、y を求めるようにしているものの、ほぼ 合計を求める関数 と同様の処理となります。さらに、最後に合計をデータの個数 n で割ってやれば、目的とする分散を求めることができます。

また、平均を求める関数 の場合は割り算を行う際にキャスト演算子を利用しましたが、calcVar で行う割り算では分子の sumdouble 型なのでキャスト演算子を利用する必要はありません。分母と分子の一方が 整数型であっても、他方が浮動小数点型であれば割り算結果も浮動小数点型となって小数点以下も正しく求めることができます。

分散を求める際の注意点

分散を求める際には、「各データと平均の差」を単に足し合わせるのではなく、「各データと平均の差の2乗」を足し合わせる必要がある点に注意してください。

分散はデータのばらつき具合を表すための指標になります。ですが「各データと平均の差」を単に足し合わせてしまうと、符号の関係でうまくばらつき具合を表すことができません。

例えば下記の5つのデータは大きなばらつきを持つデータの集合となりますが、各データと平均の差をそのまま足し合わせると結果が 0 になってしまいます。

90, 45, 50, 55, 10

要は1つ目と5つ目とでそれぞれのデータと平均の差を打ち消しあい、2つ目と4つ目とでそれぞれのデータと平均の差を打ち消し合ってしまうため 0 になってしまいます。

こんな感じで「各データと平均の差」を単純に足し合わせてしまうと各データ同士で「データと平均の差」を打ち消し合うことになるため、「各データと平均の差の2乗」を計算して符号を全て正にしてから足し合わせる必要があります。

標準偏差

最後に標準偏差の求め方について解説していきます。

スポンサーリンク

標準偏差の求め方

標準偏差も分散同様にばらつき具合を表す指標になります。

データが \( x_0 \) から \( x_{n-1} \) の \( n \) 個のデータの場合、標準偏差 \( \sigma \) は下記の式により求めることができます(\( \mu \) はデータの平均を表す)。

$$ \sigma = \sqrt{\frac{1}{n} \sum_{i = 0}^{n-1} (x_i – \mu)^2} $$

式は難しいですが、平方根の中身はそのまま 分散の求め方 で紹介した分散の計算式となります。つまり、標準偏差は分散の平方根として求めることができます。

ですので、標準偏差は 分散の求め方 に従って分散を求め、さらにその分散の平方根をとることで求めることができます。

標準偏差も分散もどちらもデータのばらつき具合を表す指標になりますが、分散の場合は2乗の計算が入るため、どれくらいデータがばらついているのかが直感的にわかりにくいです。

それに対し、その2乗の計算を打ち消すために平方根をとることで、どれくらいデータがばらついているかを直感的に分かりやすくした指標が標準偏差となります。

この辺りの標準偏差と分散の違いを理解しておけば、分散から標準偏差を求める際にどんな計算をする必要があるかを覚えやすくなると思います。

標準偏差を求める関数

標準偏差を求める関数の例は下記の calcSd となります。引数の nums を各データが格納された配列、n をデータの個数としています。

標準偏差を求める関数
double calcSd(int nums[], int n) {

    double var = calcVar(nums, n);
    double sd = sqrt(var);
    
    return sd;
}

上記では省略していますが、sqrt 関数を利用するために #include <math.h> が必要である点に注意してください。

また、calcVar分散を求める関数 で紹介した関数になります。

合計・平均・分散・標準偏差を求めるプログラム

最後に、ここまでのまとめの意味も含めて「合計・平均・分散・標準偏差」それぞれを求めるプログラムの例を紹介しておきます。

プログラム例
#include <stdio.h>
#include <math.h>

#define MAX_N 10

int calcSum(int nums[], int n) {
    int sum = 0;

    for (int i = 0; i < n; i++) {
        sum = sum + nums[i];
    }

    return sum;
}

double calcAve(int nums[], int n) {

    if (n == 0) {
        printf("nが0なので平均が計算できません\n");
        return 0;
    }

    int sum = calcSum(nums, n);
    double ave = (double)sum / (double)n;

    return ave;
}

double calcVar(int nums[], int n) {

    if (n == 0) {
        printf("nが0なので分散が計算できません\n");
        return 0;
    }

    double ave = calcAve(nums, n);
    double sum;
    
    sum = 0;
    for (int i = 0; i < n; i++) {
        double y = (nums[i] - ave) * (nums[i] - ave);
        sum = sum + y;
    }

    double var = sum / n;

    return var;
}

double calcSd(int nums[], int n) {

    double var = calcVar(nums, n);
    double sd = sqrt(var);
    
    return sd;
}


int main(void) {

    int nums[MAX_N];
    int n;

    printf("データの個数は?:");
    scanf("%d", &n);

    if (n > MAX_N) {
        printf("%d個以上のデータは扱えません...\n", MAX_N + 1);
        return -1;
    }

    for (int i = 0; i < n; i++) {
        printf("%d個目のデータ : ", i + 1);
        scanf("%d", &nums[i]);
    }


    printf("合計:%d\n", calcSum(nums, n));
    printf("平均:%f\n", calcAve(nums, n));
    printf("分散:%f\n", calcVar(nums, n));
    printf("標準偏差%f\n", calcSd(nums, n));

    return 0;
}

実行してデータの個数を入力したのちに、各データの値を入力してやれば、合計・平均・分散・標準偏差が出力されます。

データの個数の最大値は MAX_N で定義していますので、下記の 10 の部分を変更してやれば、扱えるデータの個数の最大値を変更することができます。

データの個数の最大値
#define MAX_N 10

スポンサーリンク

まとめ

このページでは、基本的な統計的指標である下記の4つをC言語で求める方法について解説しました!

  • 合計
  • 平均
  • 分散
  • 標準偏差

どれも単に式に当てはめて計算すれば良いだけではありますが、それなりにバグりやすいポイントもありますので、その点も踏まえて解説をしたつもりです。

特にC言語を使い始めたばかりの方であればハマりやすいポイントを取り上げたつもりですので、注意点などで解説した内容については是非覚えておいていただければと思います。

その他にも計算結果がうまく算出できないような場合の例や解決方法は下記ページで解説していますので、興味があればこちらも参考にしてください。

【C言語】計算結果がおかしい時の対処法まとめ

今回紹介した4つの統計的指標に関しては求める機会も多いと思いますので、求め方は是非覚えておきましょう!

また、その他の基本的な統計的指標である中央値や最頻値(モード)の求め方についても別ページで解説していますので、是非こちらも読んでみてください!

中央値を求める方法の解説ページアイキャッチ【C言語】中央値を求める方法 最頻値の求め方解説ページアイキャッチ【C言語】最頻値(モード)を求める方法

オススメの参考書

C言語学習中だけど分からないことが多くて挫折しそう...という方には、下記の「スッキリわかるC言語入門」がオススメです!

まず学習を進める上で、参考書は2冊持っておくことをオススメします。この理由は下記の2つです。

  • 参考書によって、解説の仕方は異なる
  • 読み手によって、理解しやすい解説の仕方は異なる

ある人の説明聞いても理解できなかったけど、他の人からちょっと違った観点での説明を聞いて「あー、そういうことね!」って簡単に理解できた経験をお持ちの方も多いのではないでしょうか?

それと同じで、1冊の参考書を読んで理解できない事も、他の参考書とは異なる内容の解説を読むことで理解できる可能性があります。

なので、参考書は2冊持っておいた方が学習時に挫折しにくいというのが私の考えです。

特に上記の「スッキリわかるC言語入門」は、他の参考書とは違った切り口での解説が豊富で、他の参考書で理解できなかった内容に対して違った観点での解説を読むことができ、オススメです。題名の通り「なぜそうなるのか?」がスッキリ理解できるような解説内容にもなっており、C言語入門書としてもかなり分かりやすい参考書だと思います。

もちろんネット等でも色んな観点からの解説を読むことが出来ますので、分からない点は別の人・別の参考書の解説を読んで解決していきましょう!もちろん私のサイトも参考にしていただけると嬉しいです!

入門用のオススメ参考書は下記ページでも紹介していますので、こちらも是非参考にしていただければと思います。

https://daeudaeu.com/c_reference_book/

同じカテゴリのページ一覧を表示

コメントを残す

メールアドレスが公開されることはありません。