Compute mean, variance and standard deviation of CSV number file I've written my first C++ application this week and I wanted to see if there's anything that aren't up to standards or better ways to do what I'm currently doing.

For the goal of the exercise, let's assume I can't use any other mathematical function other than the square root.

The application reads a file where numbers are on separate lines and computes the mean, variance and standard deviation of all the numbers. Afterwards I print the whole numbers list and the stats that were computed.

#include &lt;iostream&gt;
#include &lt;fstream&gt;
#include &lt;vector&gt;
#include &lt;sstream&gt;
#import &lt;cmath&gt;

using namespace std;

std::vector&lt;int&gt; readFile(const std::string &amp;filePath) {
    ifstream in_file;
    in_file.open(filePath);
    std::vector&lt;int&gt; numbers;

    std::string line;

    while(std::getline(in_file,line,'\r')){
        numbers.push_back(std::stoi(line));
    }

    return numbers;
}

float computeMean(std::vector&lt;int&gt; numbers)
{
    if(numbers.empty()) return 0;

    float total = 0;
    for (int number : numbers) {
        total += number;
    }

    return (total / numbers.size());
}

float computeVariance(float mean, std::vector&lt;int&gt; numbers)
{
    float result = 0;
    for(int number : numbers)
    {
        result += (number - mean)*(number - mean);
    }

    return result / (numbers.size() - 1);
}

int main() {
    std::cout &lt;&lt; "Please enter the file path :" &lt;&lt; std::endl;
    std::string filePath;
    std::cin &gt;&gt; filePath;
    std::vector&lt;int&gt; numbers = readFile(filePath);
    float mean = computeMean(numbers);
    float variance = computeVariance(mean, numbers);
    float standardDeviation = sqrt(variance);

    std::cout &lt;&lt; std::to_string(numbers.size()) + " numbers : ";
    std::string data;
    for(int number : numbers) {
        data += std::to_string(number) + ", ";
    }
    data = data.substr(0, data.length()-2);
    std::cout &lt;&lt; data &lt;&lt; std::endl;

    std::cout &lt;&lt; "Mean : " &lt;&lt; std::to_string(mean) &lt;&lt; std::endl;
    std::cout &lt;&lt; "Variance : " &lt;&lt; std::to_string(variance) &lt;&lt; std::endl;
    std::cout &lt;&lt; "Standard Deviation : " &lt;&lt; std::to_string(standardDeviation) &lt;&lt; std::endl;
    return 0;
}
Portability

#import is a GCC extension (or perhaps a preview of C++20).

There's no good reason not to simply #include &lt;cmath&gt; here.

Headers and namespaces

We don't use any string-stream, so #include &lt;sstream&gt; can be removed.

Bringing all names in from a namespace is problematic; namespace std particularly so.  It can silently change the meaning of your program when you're not expecting it.  Get used to using the namespace prefix (std is intentionally very short), or importing just the names you need into the smallest reasonable scope.

In this program, the only places the std:: prefix were missing were std::ifstream and std::sqrt, so this wasn't hard to fix.

readFile()

These lines can be simplified:

std::ifstream in_file;
in_file.open(filePath);


We can ask the constructor to open the file for us:

std::ifstream in_file(filePath);


This loop has some error checking, but it's not complete:

while(std::getline(in_file,line,'\r')){
    numbers.push_back(std::stoi(line));
}


Firstly, we don't expect any carriage-return in the input file (we opened it in text mode, so on systems that use CR as line delimiter, they will be converted to \n).  Secondly, std::stoi throws exceptions when the string cannot be converted, but we probably also want to check whether there are leftover, unconverted characters after our integer (e.g. if someone thought they could supply decimal values).

computeMean()

Why return a float rather than double?  Single-precision floats are normally used only where the storage size is an important consideration, which is not the case here.  (Note that on many platforms, double is the natural (and fastest) size of floating-point.)

We should pass the vector by reference to a const object, as we don't want to make a copy or to modify the value.

Instead of returning zero when there are no members, perhaps we should return a NaN value (which is more consistent with arithmetic 0.0 / 0):

if (numbers.empty())
    return std::numeric_limits&lt;double&gt;::quiet_NaN();


This loop:

double total = 0;
for (int number : numbers) {
    total += number;
}


can be written (with #include &lt;numeric&gt;) as

double total = std::accumulate(numbers.begin(), numbers.end(), 0.0);


computeVariance()

We need to be clear which variance (sample or population) we're returning.  We're also missing a size check similar to that for the mean.

Apart from that, the comments above for computeMean() are relevant:

double computeSampleVariance(const double mean, const std::vector&lt;int&gt;&amp; numbers)
{
    if (numbers.size() &lt;= 1u)
        return std::numeric_limits&lt;double&gt;::quiet_NaN();

    auto add_square = [mean](double sum, int i)
        {
            auto d = i - mean;
            return sum + d*d;
        };
    double total = std::accumulate(numbers.begin(), numbers.end(), 0.0, add_square);
    return total / (numbers.size() - 1);
}


Single-pass algorithm

It is possible to compute the mean and variances in a single pass - but not using the (exact-arithmetic) methods you likely learnt in high school, which suffer from lack of precision with the inexact floating-point types we can use.  The topic is too deep for this review, but if you research Welford's Algorithm, you will find reference implementations to guide you.

That said, for your purposes, the straightforward two-pass algorithm is probably appropriate, and it's easy to read and understand, so I wouldn't recommend changing it unless you reach a point where your input set becomes too large to hold in a vector (and even then, only if you can't read the file multiple times).



My version

#include &lt;algorithm&gt;
#include &lt;cmath&gt;
#include &lt;fstream&gt;
#include &lt;iostream&gt;
#include &lt;iterator&gt;
#include &lt;limits&gt;
#include &lt;numeric&gt;
#include &lt;vector&gt;

std::vector&lt;int&gt; readFile(const std::string&amp; filePath)
{
    std::ifstream in_file(filePath);
    std::istream_iterator&lt;int&gt; start{in_file}, end;
    std::vector&lt;int&gt; numbers;
    std::copy(start, end, std::back_inserter(numbers));
    return numbers;
}

double computeMean(const std::vector&lt;int&gt;&amp; numbers)
{
    if (numbers.empty())
        return std::numeric_limits&lt;double&gt;::quiet_NaN();

    return std::accumulate(numbers.begin(), numbers.end(), 0.0) / numbers.size();
}

double computeSampleVariance(const double mean, const std::vector&lt;int&gt;&amp; numbers)
{
    if (numbers.size() &lt;= 1u)
        return std::numeric_limits&lt;double&gt;::quiet_NaN();

    auto const add_square = [mean](double sum, int i) {
        auto d = i - mean;
        return sum + d*d;
    };
    double total = std::accumulate(numbers.begin(), numbers.end(), 0.0, add_square);
    return total / (numbers.size() - 1);
}

int main()
{
#ifdef TEST
    const std::vector&lt;int&gt; numbers = { -2, -1, 1, 2, 100000-2, 100000-1, 100000+1, 100000+2};
#else
    std::cout &lt;&lt; "Please enter the file path :" &lt;&lt; std::endl;
    std::string filePath;
    std::cin &gt;&gt; filePath;
    const std::vector&lt;int&gt; numbers = readFile(filePath);
#endif

    double mean = computeMean(numbers);
    double variance = computeSampleVariance(mean, numbers);
    double standardDeviation = std::sqrt(variance);

    std::cout &lt;&lt; numbers.size() &lt;&lt; " numbers : ";
    auto separator = "";
    for (int number: numbers) {
        std::cout &lt;&lt; separator &lt;&lt; number;
        separator = ", ";
    }
    std::cout &lt;&lt; std::endl;

    std::cout &lt;&lt; "Mean : " &lt;&lt; std::to_string(mean)
              &lt;&lt; "Variance : " &lt;&lt; std::to_string(variance)
              &lt;&lt; "Standard Deviation : " &lt;&lt; std::to_string(standardDeviation) &lt;&lt; std::endl;
    return 0;
}