raw Software
RAW Software Libraries Numerical Computing

Read CSV Data into an Eigen Matrix

Robert Eisele

A CSV table is not a matrix yet. The parser must first establish a rectangular table and convert every field to a number; only then is it safe to allocate an Eigen matrix.

The function below combines Eigen 3 with csv-parser. The parser handles quoted fields, escaped quotes, embedded line breaks, UTF-8 byte-order marks, and the line-ending variants covered by RFC 4180. Explicit format settings disable header inference and make rows with inconsistent widths fail instead of disappearing silently.

#include <Eigen/Dense>
#include "csv.hpp"

#include <cstddef>
#include <stdexcept>
#include <string>
#include <vector>

Eigen::MatrixXd readCsvMatrix(const std::string& filename) {
    csv::CSVFormat format;
    format.delimiter(',')
          .no_header()
          .variable_columns(csv::VariableColumnPolicy::THROW);

    csv::CSVReader reader(filename, format);
    std::vector<double> values;
    std::size_t rowCount = 0;
    std::size_t columnCount = 0;

    for (csv::CSVRow& row : reader) {
        if (rowCount == 0) {
            columnCount = row.size();
            if (columnCount == 0) {
                throw std::runtime_error("CSV file contains an empty row");
            }
        }

        for (csv::CSVField& field : row) {
            if (field.get<std::string>().empty()) {
                throw std::runtime_error("CSV matrix contains an empty field");
            }
            values.push_back(field.get<double>());
        }
        ++rowCount;
    }

    if (rowCount == 0) {
        throw std::runtime_error("CSV file contains no rows");
    }

    Eigen::MatrixXd matrix(
        static_cast<Eigen::Index>(rowCount),
        static_cast<Eigen::Index>(columnCount)
    );

    for (std::size_t row = 0; row < rowCount; ++row) {
        for (std::size_t column = 0; column < columnCount; ++column) {
            matrix(
                static_cast<Eigen::Index>(row),
                static_cast<Eigen::Index>(column)
            ) = values[row * columnCount + column];
        }
    }

    return matrix;
}

For a file containing

1.5,2,3
4,5.25,6

readCsvMatrix("matrix.csv") returns the matrix

\[ \begin{pmatrix} 1.5 & 2 & 3 \\ 4 & 5.25 & 6 \end{pmatrix}. \]

Why the shape is inferred

Passing row and column counts separately duplicates information already present in the input. It also creates a dangerous failure mode: too few values leave part of the result undefined at the application level, while too many values can write outside an unchecked buffer. Here, the first row determines the width and the parser rejects every later row with a different number of fields.

Conversion failures

CSVField::get<double>() accepts decimal and scientific notation and throws when a field is not a valid floating-point value or lies outside the representable range. Empty fields are rejected explicitly. Applications that use missing values should choose and implement a policy, such as replacing them with NaN, before constructing the matrix.

The function materializes all scalar values once because an Eigen matrix needs its final dimensions at construction time. For data sets that do not fit comfortably in memory, process CSV rows as a stream instead of forcing the complete table into one dense matrix.

Building

Install Eigen and add the single-header csv.hpp distribution to the include path. A minimal C++17 build on a system with Eigen registered through pkg-config is:

c++ -std=c++17 example.cpp -Iinclude $(pkg-config --cflags eigen3) -pthread

This function intentionally expects a headerless, comma-delimited numeric table. Configure CSVFormat explicitly when a file has a header, another delimiter, comments, or a different whitespace policy.