public boolean push_back(ArrayList<Double> sample) {
        //If there is no data, but we know how many cols are in a sample then we simply create a new buffer of size 1 and add the sample
        if (dataPtr == null) {
            cols = (int) sample.size();
            if (!resize(1, cols)) {
                clear();
                return false;
            }
            for (int j = 0; j < cols; j++) {
                dataPtr[0][j] = sample.get(j);
            }
            return true;
        }

        //If there is data and the sample size does not match the number of columns then return false
        if (sample.size() != cols) {
            return false;
        }

        //Check to see if we have reached the capacity, if not then simply add the new data
        if (rows < capacity) {
            //Add the new sample at the end
            for (int j = 0; j < cols; j++) {
                dataPtr[rows][j] = sample.get(j);
            }

        } else { //Otherwise we copy the existing data from the data ptr into a new buffer of size (rows+1) and add the sample at the end
            double[][] tempDataPtr = new double[rows + 1][cols];

            //Copy the original data
            for (int i = 0; i < rows; i++) {
                for (int j = 0; j < cols; j++) {
                    tempDataPtr[i][j] = dataPtr[i][j];
                }
            }

            //Add the new sample at the end
            for (int j = 0; j < cols; j++) {
                tempDataPtr[rows][j] = sample.get(j);
            }

            dataPtr = tempDataPtr;

            //Increment the capacity so it matches the number of rows
            capacity++;
        }

        //Increment the number of rows
        rows++;

        //Finally return true to signal that the data was added correctly
        return true;
    }