diff --git a/docs/HTML/detect_and_change.html b/docs/HTML/detect_and_change.html
index 778df2a8..f27603ab 100644
--- a/docs/HTML/detect_and_change.html
+++ b/docs/HTML/detect_and_change.html
@@ -187,14 +187,14 @@
open_col[3000] = 2.5;
open_col[5029] = 850.0;
- ibm.detect_and_change<double>({ "IBM_Close", "IBM_Open" }, detect_method::fft, fill_policy::mid_point, { .threshold = 250.0, .norm_type = normalization_type::z_score, .freq_num = 1000 });
-
- assert((std::fabs(close_col[502] - 82.02) < 0.01));
- assert((std::fabs(close_col[1001] - 89.805) < 0.01));
- assert((std::fabs(close_col[2002] - 88.055) < 0.01));
- assert((std::fabs(open_col[2] - 1.0) < 0.01)); // It didn't catch it
- assert((std::fabs(open_col[3000] - 2.5) < 0.01)); // It didn't catch it
- assert((std::fabs(open_col[5029] - 108.28) < 0.01));
+ ibm.detect_and_change<double>({ "IBM_Close", "IBM_Open" }, detect_method::fft, fill_policy::mid_point, { .threshold = 80.0, .norm_type = normalization_type::none, .freq_num = 1000 });
+
+ assert((std::fabs(close_col[502] - 260.87) < 0.001));
+ assert((std::fabs(close_col[1001] - 292.555) < 0.001));
+ assert((std::fabs(close_col[2002] - 276.995) < 0.001));
+ assert((std::fabs(open_col[2] - 1.0) < 0.01)); // Didn't catch this
+ assert((std::fabs(open_col[3000] - 210.15) < 0.01));
+ assert((std::fabs(open_col[5029] - 294.488) < 0.01));
}
// Now we need a DataFrame with a numeric index to be able to use
@@ -226,8 +226,8 @@
assert((std::fabs(fclose_col[502] - 1.6889) < 0.0001));
assert((std::fabs(fclose_col[1001] - 1.8146) < 0.0001));
assert((std::fabs(fclose_col[2002] - 0.9022) < 0.0001));
- assert((std::fabs(fopen_col[2] - 0.01) < 0.01)); // It didn't catch it
- assert((std::fabs(fopen_col[3000] - 0.05) < 0.01)); // It didn't catch it
+ assert((std::fabs(fopen_col[2] - 0.01) < 0.01)); // No catch
+ assert((std::fabs(fopen_col[3000] - 0.05) < 0.01)); // No catch
assert((std::fabs(fopen_col[5029] - 7.9947) < 0.0001));
}
}
diff --git a/docs/HTML/remove_data_by_fft.html b/docs/HTML/remove_data_by_fft.html
index a0f1b299..5aaa9e2f 100644
--- a/docs/HTML/remove_data_by_fft.html
+++ b/docs/HTML/remove_data_by_fft.html
@@ -128,11 +128,11 @@
assert((ibm.get_column<double>("IBM_Open").size() == 5031));
assert((ibm_view.get_column<double>("IBM_Open").size() == 5031));
- ibm.remove_data_by_fft<double, double, long>("IBM_Close", 1000, 250, normalization_type::z_score);
- assert((ibm.get_column<double>("IBM_Open").size() == (5031 - 3)));
+ ibm.remove_data_by_fft<double, double, long>("IBM_Close", 1000, 80);
+ assert((ibm.get_column<double>("IBM_Open").size() == (5031 - 9)));
- ibm_view.remove_data_by_fft<double, double, long>("IBM_Close", 1000, 250, normalization_type::z_score);
- assert((ibm_view.get_column<double>("IBM_Open").size() == (5031 - 3)));
+ ibm_view.remove_data_by_fft<double, double, long>("IBM_Close", 1000, 80);
+ assert((ibm_view.get_column<double>("IBM_Open").size() == (5031 - 9)));
}
diff --git a/include/DataFrame/DataFrameMLVisitors.h b/include/DataFrame/DataFrameMLVisitors.h
index 9300d898..23d7ffe3 100644
--- a/include/DataFrame/DataFrameMLVisitors.h
+++ b/include/DataFrame/DataFrameMLVisitors.h
@@ -1725,7 +1725,7 @@ struct EntropyVisitor {
using value_type = T;
using index_type = I;
- using size_type = std::size_t;
+ using size_type = std::size_t;
using result_type =
typename std::conditional_t,
@@ -1914,7 +1914,7 @@ struct ImpurityVisitor {
using value_type = T;
using index_type = I;
- using size_type = std::size_t;
+ using size_type = std::size_t;
using result_type = vec_t;
template
@@ -2064,7 +2064,7 @@ struct SigmoidVisitor {
using value_type = T;
using index_type = I;
- using size_type = std::size_t;
+ using size_type = std::size_t;
using result_type =
typename std::conditional_t,
@@ -2555,7 +2555,7 @@ struct PolicyLearningLossVisitor {
using value_type = T;
using index_type = I;
- using size_type = std::size_t;
+ using size_type = std::size_t;
using result_type =
typename std::conditional_t,
@@ -2708,7 +2708,7 @@ struct LossFunctionVisitor {
using value_type = T;
using index_type = I;
- using size_type = std::size_t;
+ using size_type = std::size_t;
using result_type =
typename std::conditional_t>;
@@ -3124,7 +3124,7 @@ struct VectorSimilarityVisitor {
using value_type = T;
using index_type = I;
- using size_type = std::size_t;
+ using size_type = std::size_t;
using result_type = double;
template
@@ -3491,7 +3491,7 @@ struct SpectralClusteringVisitor {
// Update the accessible data
//
for (size_type i = 0; i < K; ++i) {
- const auto val = col_s / K + 10;
+ const auto val = col_s / K + 10;
clusters_[i].reserve(val);
clusters_idxs_[i].reserve(val);
@@ -3545,7 +3545,7 @@ struct SeasonalPeriodVisitor {
using value_type = T;
using index_type = I;
- using size_type = std::size_t;
+ using size_type = std::size_t;
using result_type = double;
using md_result_type = std::vector;
@@ -3788,7 +3788,7 @@ struct DynamicTimeWarpVisitor {
using value_type = T;
using index_type = I;
- using size_type = std::size_t;
+ using size_type = std::size_t;
using result_type = double;
using distance_func = std::function;
@@ -3960,7 +3960,7 @@ struct AnomalyDetectByFFTVisitor {
fft.post();
// fft_res type:
- // scalar --> channel_result_t = vec>
+ // scalar --> channel_result_t = vec>
// MD --> Matrix
//
auto fft_res = std::move(fft.get_result());
@@ -5577,7 +5577,7 @@ struct LSTMForecastVisitor {
private:
- static constexpr bool is_md_ = random_acc_cont;
+ static constexpr bool is_md_ { random_acc_cont };
using data_t =
typename std::conditional_t data_t {
data_t sum { 0 };
@@ -6159,8 +6160,7 @@ struct LSTMForecastVisitor {
if constexpr (! is_md_) {
for (long i { 0 }; i < long(col_s); ++i)
- norm_data[i][0] =
- (*(column_begin + i) - mean) / std_dev;
+ norm_data[i][0] = (*(column_begin + i) - mean) / std_dev;
}
else {
for (long i { 0 }; i < long(col_s); ++i) {
@@ -6381,11 +6381,11 @@ struct AnomalyDetectByKNNVisitor {
// KDTree::point_t is std::vector.
// For the scalar path, a "window" flattened to vector is
- // just {x[0], x[1], ..., x[window-1]}.
+ // just { x[0], x[1], ..., x[window-1] }.
// For the MD path, a "window" is the concatenation of all inner elements
- // across the window: {x[0][0],...,x[0][d-1], x[1][0],...,x[w-1][d-1]}.
+ // across the window: { x[0][0],...,x[0][d-1], x[1][0],...,x[w-1][d-1] }.
//
- using tree_point_t = std::vector;
+ using tree_point_t = std::vector;
using tree_points_t = std::vector;
using distance_func =
@@ -6427,9 +6427,8 @@ struct AnomalyDetectByKNNVisitor {
for (size_type i { 1 }; i < col_s; ++i)
if ((column_begin + i)->size() != dim)
- throw DataFrameError(
- "AnomalyDetectByKNNVisitor: "
- "Inconsistent data dimensions");
+ throw DataFrameError("AnomalyDetectByKNNVisitor: "
+ "Inconsistent data dimensions");
}
#endif // HMDF_SANITY_EXCEPTIONS
@@ -6524,10 +6523,10 @@ struct AnomalyDetectByKNNVisitor {
// tree_point_t (std::vector) that KDTree expects.
//
// Scalar path (T = double):
- // value_type = double, one element per position → [v0, v1, ..., v_{w-1}]
+ // value_type = double, one element per position -> [v0, v1, ..., v_{w-1}]
//
// MD path (T = std::vector or std::array):
- // Each position contributes its inner elements concatenated →
+ // Each position contributes its inner elements concatenated ->
// [v0[0],...,v0[d-1], v1[0],...,v_{w-1}[d-1]]
//
template
@@ -6743,9 +6742,9 @@ struct BIRCHVisitor {
inline void
calc_clusters_(const H &column_begin, size_type col_s) {
- order_type clusters_idxs(k_);
+ order_type clusters_idxs(result_.size());
- for (size_type i { 0 }; i < k_; ++i) [[likely]]
+ for (size_type i { 0 }; i < size_type(result_.size()); ++i) [[likely]]
clusters_idxs[i].reserve(col_s / k_ + 2);
for (size_type j { 0 }; j < col_s; ++j) [[likely]] {
@@ -6755,7 +6754,7 @@ struct BIRCHVisitor {
double min_dist { std::numeric_limits::max() };
size_type min_idx { 0 };
- for (size_type i { 0 }; i < k_; ++i) {
+ for (size_type i { 0 }; i < size_type(result_.size()); ++i) {
const double dist { dfunc_(value, result_[i]) };
if (dist < min_dist) {
@@ -6789,15 +6788,19 @@ struct BIRCHVisitor {
double current_threshold { threshold_ };
CFTree tree { };
-
if constexpr (! is_md_) {
- CFTree tmp_tree { threshold_, 1, get_dist_func_() };
+ CFTree tmp_tree {
+ threshold_, 1, get_dist_func_(), max_entries_
+ };
tree = std::move(tmp_tree);
}
else {
CFTree tmp_tree {
- threshold_, size_type(column_begin->size()), get_dist_func_()
+ threshold_,
+ size_type(column_begin->size()),
+ get_dist_func_(),
+ max_entries_
};
tree = std::move(tmp_tree);
@@ -6806,6 +6809,8 @@ struct BIRCHVisitor {
for (size_type i { 0 }; i < size_type(col_s); ++i) {
const value_type &val { *(column_begin + i) };
+ if (is_nan__(val)) continue;
+
while (! tree.insert(val)) {
// Tree is full, rebuild with larger threshold
//
@@ -6891,8 +6896,8 @@ struct BIRCHVisitor {
// and a known value, fit the Kriging system so you can later call predict() at
// any arbitrary location and get back a value plus its uncertainty.
//
-template
+template
struct KrigingVisitor {
public:
@@ -6950,7 +6955,7 @@ struct KrigingVisitor {
// recurrence starting from K_0 and K_1 computed through the standard
// rational Chebyshev approximations (Abramowitz & Stegun 9.8).
//
- auto k0 = [](double t) -> double {
+ auto k0 = [](double t) -> double {
if (t <= 2.0) {
const double y { t * t / 4.0 };
@@ -6970,7 +6975,7 @@ struct KrigingVisitor {
z * (-0.00251540 + z * 0.00053208)))))));
}
};
- auto k1 = [](double t) -> double {
+ auto k1 = [](double t) -> double {
if (t <= 2.0) {
const double y { t * t / 4.0 };
@@ -7004,7 +7009,7 @@ struct KrigingVisitor {
const size_type n { static_cast(std::floor(nu)) };
if (n == 0) return (k_prev);
- for (size_type i = 1; i < n; ++i) {
+ for (size_type i { 1 }; i < n; ++i) {
const double k_next { (2.0 * i / x) * k_curr + k_prev };
k_prev = k_curr;
@@ -7051,7 +7056,7 @@ struct KrigingVisitor {
distance_(const coord_type &p1, const coord_type &p2) const noexcept {
if constexpr (DIM == 2) {
- if (params_.anisotropy_ratio < value_type(1) - value_type(1e-12)) {
+ if (params_.anisotropy_ratio < (T(1) - T(1e-12))) {
const value_type dx { p1[0] - p2[0] };
const value_type dy { p1[1] - p2[1] };
const value_type ca { std::cos(params_.anisotropy_angle) };
@@ -7083,8 +7088,7 @@ struct KrigingVisitor {
//
inline value_type matern_corr_(value_type h) const {
- if (h <= 0) [[unlikely]]
- return (value_type(1));
+ if (h <= 0) [[unlikely]] return (value_type(1));
const double nu { static_cast(params_.matern_smoothness) };
const double r { static_cast(params_.range) };
@@ -7104,8 +7108,7 @@ struct KrigingVisitor {
else if (std::fabs(nu - 2.5) < 1e-9) {
const double s5 { std::sqrt(5.0) * hd / r };
- return (static_cast(
- (1.0 + s5 + (s5 * s5) / 3.0) * std::exp(-s5)));
+ return (value_type((1.0 + s5 + (s5 * s5) / 3.0) * std::exp(-s5)));
}
else if (nu > 50.0) {
// Numerically converges to the Gaussian model in this limit.
@@ -7215,8 +7218,7 @@ struct KrigingVisitor {
obs_ = obs;
const size_type dim_sys { params_.ordinary ? n + 1 : n };
-
- matrix_t K { dim_sys, dim_sys, 0 };
+ matrix_t K { dim_sys, dim_sys, 0 };
for (size_type i { 0 }; i < n; ++i) {
K(i, i) = covariance_(value_type(0)) + params_.ridge;
@@ -7255,16 +7257,19 @@ struct KrigingVisitor {
vec_t y(sys_dim_, 0), z(sys_dim_, 0), x(sys_dim_, 0);
for (size_type i { 0 }; i < sys_dim_; ++i) {
- y[i] = rhs[i];
+ auto &iy_val { y[i] };
+
+ iy_val = rhs[i];
for (size_type j { 0 }; j < i; ++j)
- y[i] -= lower_(i, j) * y[j];
+ iy_val -= lower_(i, j) * y[j];
+ z[i] = iy_val / diag_[i];
}
- for (size_type i { 0 }; i < sys_dim_; ++i)
- z[i] = y[i] / diag_[i];
for (size_type i { sys_dim_ - 1 }; i >= 0; --i) {
- x[i] = z[i];
+ auto &ix_val { x[i] };
+
+ ix_val = z[i];
for (size_type j { i + 1 }; j < sys_dim_; ++j)
- x[i] -= lower_(j, i) * x[j];
+ ix_val -= lower_(j, i) * x[j];
}
return (x);
}
@@ -7305,9 +7310,9 @@ struct KrigingVisitor {
// The core BLUE (Best Linear Unbiased Estimator) computation against
// whatever system (global or a local neighborhood) is currently cached:
- // weights = K^-1 * k0 (k0 = covariances to the query point)
- // value = weights . observations (+ Lagrange handling)
- // variance = C(0) - k0 . weights (- mu, for Ordinary Kriging)
+ // weights = K^-1 * k0 (k0 = covariances to the query point)
+ // value = weights . observations (+ Lagrange handling)
+ // variance = C(0) - k0 . weights (- mu, for Ordinary Kriging)
//
Estimate predict_global_(const coord_type &query_pt) const {
@@ -7371,9 +7376,8 @@ struct KrigingVisitor {
#ifdef HMDF_SANITY_EXCEPTIONS
if (col_s != size_type(std::distance(obs_begin, obs_end)))
- throw DataFrameError(
- "KrigingVisitor: coordinate and observation "
- "columns must have equal length");
+ throw DataFrameError("KrigingVisitor: coordinate and observation "
+ "columns must have equal length");
#endif // HMDF_SANITY_EXCEPTIONS
params_.sanity_check();
@@ -7393,8 +7397,8 @@ struct KrigingVisitor {
bool has_nan { false };
for (size_type d { 0 }; d < size_type(DIM); ++d)
- if (is_nan__((*cit)[d])) [[unlikely]] { has_nan = true; break; }
- if (has_nan) [[unlikely]] continue;
+ if (is_nan__((*cit)[d])) [[unlikely]] { has_nan = true; break; }
+ if (has_nan) [[unlikely]] continue;
coords.push_back(*cit);
obs.push_back(*oit);
@@ -7453,9 +7457,9 @@ struct KrigingVisitor {
//
template
inline result_type
- predict_many (const QV &begin, const QV &end) const {
+ predict_many(const QV &begin, const QV &end) const {
- result_type result(static_cast(std::distance(begin, end)));
+ result_type result { size_type(std::distance(begin, end)) };
size_type i { 0 };
for (auto it { begin }; it != end; ++it)
@@ -7471,19 +7475,54 @@ struct KrigingVisitor {
//
inline vec_t loo_cross_validate () const {
- const size_type n { size_type(coords_.size()) };
- vec_t residuals(n, 0);
+ const size_type n { size_type(coords_.size()) };
+ vec_t residuals(n, 0);
+ const bool local {
+ params_.max_neighbors > 0 &&
+ size_type(params_.max_neighbors) < (n - 1)
+ };
+ const size_type k {
+ local ? size_type(params_.max_neighbors) : (n - 1)
+ };
+ vec_t train_c;
+ vec_t train_o;
+ vec_t> dists;
+ train_c.reserve(local ? k : n - 1);
+ train_o.reserve(local ? k : n - 1);
+ dists.reserve(local ? n - 1 : 0);
for (size_type leave_out { 0 }; leave_out < n; ++leave_out) {
- vec_t train_c;
- vec_t train_o;
+ train_c.clear();
+ train_o.clear();
- train_c.reserve(n - 1);
- train_o.reserve(n - 1);
- for (size_type i = 0; i < n; ++i) {
- if (i == leave_out) continue;
- train_c.push_back(coords_[i]);
- train_o.push_back(obs_[i]);
+ // Restrict each refit to the max_neighbors closest other points,
+ // mirroring predict_local_, so loo-cv stays tractable for large N
+ // under local-Kriging configurations (each refit becomes O(k^3)
+ // instead of O(n^3)) instead of silently ignoring max_neighbors.
+ //
+ if (local) {
+ dists.clear();
+ for (size_type i { 0 }; i < n; ++i) {
+ if (i == leave_out) continue;
+ dists.push_back(
+ { distance_(coords_[i], coords_[leave_out]), i });
+ }
+ std::partial_sort(
+ dists.begin(), dists.begin() + k, dists.end(),
+ [](const auto &a, const auto &b) {
+ return (a.first < b.first);
+ });
+ for (size_type i { 0 }; i < k; ++i) {
+ train_c.push_back(coords_[dists[i].second]);
+ train_o.push_back(obs_[dists[i].second]);
+ }
+ }
+ else {
+ for (size_type i = 0; i < n; ++i) {
+ if (i == leave_out) continue;
+ train_c.push_back(coords_[i]);
+ train_o.push_back(obs_[i]);
+ }
}
KrigingVisitor loo(params_);
@@ -7496,7 +7535,7 @@ struct KrigingVisitor {
}
return (residuals);
}
-
+
explicit
KrigingVisitor(const KrigingParams ¶ms = { })
: params_(params) { }
@@ -7546,7 +7585,7 @@ struct SilhouetteScoreVisitor {
return ([](const T &x, const T &y) -> double {
double sum { 0.0 };
- for (size_type i { 0 }; i < size_type(x.size()); ++i) {
+ for (size_type i { 0 }; i < size_type(x.size()); ++i) {
const double diff {
static_cast(x[i]) -
static_cast(y[i])
@@ -7558,8 +7597,8 @@ struct SilhouetteScoreVisitor {
});
}
- static constexpr long NOISE_1 { -1L }; // DBSCAN unclassified sentinel
- static constexpr long NOISE_2 { -2L }; // DBSCAN noise sentinel
+ static constexpr long NOISE_1 { -1L }; // DBSCAN unclassified sentinel
+ static constexpr long NOISE_2 { -2L }; // DBSCAN noise sentinel
public:
@@ -7567,7 +7606,7 @@ struct SilhouetteScoreVisitor {
//
template
inline void
- operator()(const K &idx_begin, const K &idx_end,
+ operator()(const K &idx_begin, const K &idx_end,
const H1 &col_begin, const H1 &col_end,
const H2 &lbl_begin, const H2 &lbl_end) {
@@ -7600,18 +7639,16 @@ struct SilhouetteScoreVisitor {
cluster_ids.push_back(lbl);
}
std::sort(cluster_ids.begin(), cluster_ids.end());
- cluster_ids.erase(
- std::unique(cluster_ids.begin(), cluster_ids.end()),
- cluster_ids.end());
+ cluster_ids.erase(std::unique(cluster_ids.begin(), cluster_ids.end()),
+ cluster_ids.end());
const size_type n_clusters { cluster_ids.size() };
- // Fewer than 2 meaningful clusters → silhouette is undefined
+ // Fewer than 2 meaningful clusters -> silhouette is undefined
//
- if (n_clusters < 2) [[unlikely]]
- return;
+ if (n_clusters < 2) [[unlikely]] return;
- // Build cluster label → ordinal map for O(log k) lookup
+ // Build cluster label -> ordinal map for O(log k) lookup
//
std::vector> label_to_ord;
@@ -7622,9 +7659,11 @@ struct SilhouetteScoreVisitor {
auto label_ord =
[&label_to_ord = std::as_const(label_to_ord)]
(long lbl) -> size_type {
- auto it = std::lower_bound(
- label_to_ord.begin(), label_to_ord.end(), lbl,
- [](const auto &p, long v) { return p.first < v; });
+ const auto it {
+ std::lower_bound(
+ label_to_ord.begin(), label_to_ord.end(), lbl,
+ [](const auto &p, long v) { return (p.first < v); })
+ };
return (it->second);
};
@@ -7651,24 +7690,23 @@ struct SilhouetteScoreVisitor {
// per-point, per-cluster distance sums
// dist_sums[i][c] = Σⱼ∈cluster_c dist(i, j)
//
- std::vector>
- dist_sums(col_s, std::vector(n_clusters, 0.0));
+ std::vector> dist_sums(
+ col_s, std::vector(n_clusters, 0.0)
+ );
for (size_type i { 0 }; i < col_s; ++i) {
const long lbl { *(lbl_begin + i) };
- if (lbl == NOISE_1 || lbl == NOISE_2) [[unlikely]]
- continue;
+ if (lbl == NOISE_1 || lbl == NOISE_2) [[unlikely]] continue;
const value_type &vi { *(col_begin + i) };
for (size_type j { 0 }; j < col_s; ++j) {
- if (i == j) [[unlikely]] continue;
+ if (i == j) [[unlikely]] continue;
const long lbl { *(lbl_begin + j) };
- if (lbl == NOISE_1 || lbl == NOISE_2) [[unlikely]]
- continue;
+ if (lbl == NOISE_1 || lbl == NOISE_2) [[unlikely]] continue;
const size_type c_j { label_ord(lbl) };
@@ -7684,13 +7722,12 @@ struct SilhouetteScoreVisitor {
for (size_type i { 0 }; i < col_s; ++i) {
const long lbl_i { *(lbl_begin + i) };
- if (lbl_i == NOISE_1 || lbl_i == NOISE_2) [[unlikely]]
- continue;
+ if (lbl_i == NOISE_1 || lbl_i == NOISE_2) [[unlikely]] continue;
const size_type c_i { label_ord(lbl_i) };
const size_type sz_i { cluster_sz[c_i] };
- // Singleton: a(i) undefined → s(i) = 0
+ // Singleton: a(i) undefined -> s(i) = 0
//
if (sz_i < 2) [[unlikely]] {
++scored_cnt;
@@ -7701,11 +7738,10 @@ struct SilhouetteScoreVisitor {
// b(i) = mean distance to the nearest other cluster
//
- double b_i { std::numeric_limits::max() };
+ double b_i { std::numeric_limits::max() };
for (size_type c { 0 }; c < n_clusters; ++c) {
- if (c == c_i) [[unlikely]] continue;
- if (cluster_sz[c] == 0) [[unlikely]] continue;
+ if (c == c_i || cluster_sz[c] == 0) [[unlikely]] continue;
const double mean_d {
dist_sums[i][c] / double(cluster_sz[c])
@@ -7718,8 +7754,8 @@ struct SilhouetteScoreVisitor {
const double denom { std::max(a_i, b_i) };
const double s_i { (denom > 0.0) ? (b_i - a_i) / denom : 0.0 };
- scores_[i] = s_i;
- score_sum += s_i;
+ scores_[i] = s_i;
+ score_sum += s_i;
++scored_cnt;
}
@@ -7773,7 +7809,6 @@ struct DaviesBouldinIndexVisitor {
template
using vec_t = std::vector::type>;
- // Centroid type: double for scalar T, std::vector for MD T
using centroid_t =
std::conditional_t>;
@@ -7817,6 +7852,23 @@ struct DaviesBouldinIndexVisitor {
// This ensures the user-supplied distance function is used consistently
// for both intra-cluster scatter and inter-centroid separation.
//
+ // Known limitation for non-floating-point T (e.g. int, long): centroids
+ // are inherently fractional (a sum divided by a count), but dfunc_'s
+ // signature is double(const T&, const T&), so a centroid must be cast
+ // back to T before every dfunc_ call here and in the scatter loop below
+ // -- silently truncating it. This is unavoidable without either an
+ // API-breaking change to distance_func's signature or dropping support
+ // for user-supplied custom distance functions (both scatter_ and
+ // centroid_dist_ must route through whatever dfunc_ the caller provided
+ // to honor a custom distance, and there is no way to know at runtime
+ // whether dfunc_ is the default or user-supplied). Callers with
+ // non-floating-point T should be aware DB-index/scatter values will be
+ // computed against truncated centroids; supplying an explicit
+ // floating-point-aware distance function does not avoid this, since the
+ // centroid itself is still cast to T before being passed in. This
+ // mirrors the equivalent, already-documented caveat for non-double MD
+ // element types below.
+ //
inline double
centroid_dist_(const centroid_t &a, const centroid_t &b) const {
@@ -7849,7 +7901,7 @@ struct DaviesBouldinIndexVisitor {
template
inline void
- operator()(const K &idx_begin, const K &idx_end,
+ operator()(const K &idx_begin, const K &idx_end,
const H1 &col_begin, const H1 &col_end,
const H2 &lbl_begin, const H2 &lbl_end) {
@@ -7882,14 +7934,14 @@ struct DaviesBouldinIndexVisitor {
cluster_ids.push_back(lbl);
}
std::sort(cluster_ids.begin(), cluster_ids.end());
- cluster_ids.erase(
- std::unique(cluster_ids.begin(), cluster_ids.end()),
- cluster_ids.end());
+ cluster_ids.erase(std::unique(cluster_ids.begin(), cluster_ids.end()),
+ cluster_ids.end());
const size_type k { cluster_ids.size() };
- if (k < 2) [[unlikely]]
- return; // DB undefined for fewer than 2 clusters
+ // DB undefined for fewer than 2 clusters
+ //
+ if (k < 2) [[unlikely]] return;
// label -> ordinal (binary-search on sorted cluster_ids)
//
@@ -7914,7 +7966,7 @@ struct DaviesBouldinIndexVisitor {
for (size_type i { 0 }; i < col_s; ++i) {
const long lbl { *(lbl_begin + i) };
- if (lbl == NOISE_1 || lbl == NOISE_2) [[unlikely]] continue;
+ if (lbl == NOISE_1 || lbl == NOISE_2) [[unlikely]] continue;
const size_type c { label_ord(lbl) };
@@ -7933,8 +7985,7 @@ struct DaviesBouldinIndexVisitor {
for (size_type i { 0 }; i < col_s; ++i) {
const long lbl { *(lbl_begin + i) };
- if (lbl == NOISE_1 || lbl == NOISE_2)
- continue;
+ if (lbl == NOISE_1 || lbl == NOISE_2) continue;
dim = size_type((col_begin + i)->size());
break;
}
@@ -7944,7 +7995,7 @@ struct DaviesBouldinIndexVisitor {
for (size_type i { 0 }; i < col_s; ++i) {
const long lbl { *(lbl_begin + i) };
- if (lbl == NOISE_1 || lbl == NOISE_2) [[unlikely]] continue;
+ if (lbl == NOISE_1 || lbl == NOISE_2) [[unlikely]] continue;
const size_type c { label_ord(lbl) };
const value_type &pt { *(col_begin + i) };
@@ -7973,7 +8024,7 @@ struct DaviesBouldinIndexVisitor {
for (size_type i { 0 }; i < col_s; ++i) {
const long lbl { *(lbl_begin + i) };
- if (lbl == NOISE_1 || lbl == NOISE_2) [[unlikely]] continue;
+ if (lbl == NOISE_1 || lbl == NOISE_2) [[unlikely]] continue;
const size_type c { label_ord(lbl) };
@@ -7988,7 +8039,7 @@ struct DaviesBouldinIndexVisitor {
for (size_type i { 0 }; i < col_s; ++i) {
const long lbl { *(lbl_begin + i) };
- if (lbl == NOISE_1 || lbl == NOISE_2) [[unlikely]] continue;
+ if (lbl == NOISE_1 || lbl == NOISE_2) [[unlikely]] continue;
const size_type c { label_ord(lbl) };
const value_type &pt { *(col_begin + i) };
@@ -8001,7 +8052,7 @@ struct DaviesBouldinIndexVisitor {
sq += diff * diff;
}
- scatter_[c] += std::sqrt(sq); // Euclidean, consistent
+ scatter_[c] += std::sqrt(sq); // Euclidean, consistent
}
}
@@ -8049,7 +8100,7 @@ struct DaviesBouldinIndexVisitor {
worst_ratio_.clear();
centroids_.clear();
}
- inline void post() { }
+ inline void post() { }
// The Davies-Bouldin index ∈ [0, ∞). Lower is better.
//
@@ -8118,8 +8169,14 @@ struct CalinskiHarabaszVisitor {
private:
- // Default: squared Euclidean for scalar (matches KMeans/DBSCAN/DB),
- // Euclidean for MD.
+ // Default: squared Euclidean for both scalar and MD (matches
+ // KMeans/DBSCAN/DB's own scalar default). Calinski-Harabasz is a
+ // variance-ratio statistic -- BGSS and WGSS are explicitly sums of
+ // squares by definition (see the formula comments in operator() below)
+ // -- so, unlike Silhouette/Davies-Bouldin where squared-vs-raw distance
+ // is a free choice of metric, using raw (rooted) Euclidean distance for
+ // MD here would not merely rescale the result: it would stop computing
+ // a sum of squares at all, breaking the statistic's own definition.
//
inline static distance_func
default_dist_() {
@@ -8139,13 +8196,15 @@ struct CalinskiHarabaszVisitor {
sum += diff * diff;
}
- return (std::sqrt(sum));
+ return (sum);
});
}
// Distance between two centroid_t values — routes through dfunc_ for
// scalar so that custom distance functions apply to centroid comparisons
- // (same fix applied in DaviesBouldinIndexVisitor).
+ // (same fix applied in DaviesBouldinIndexVisitor). Returns squared
+ // distance for MD too, to match default_dist_ and the sum-of-squares
+ // definitions of BGSS/WGSS below -- see default_dist_'s comment.
//
inline double
centroid_dist_(const centroid_t &a, const centroid_t &b) const {
@@ -8160,7 +8219,7 @@ struct CalinskiHarabaszVisitor {
sum += diff * diff;
}
- return (std::sqrt(sum));
+ return (sum);
}
}
@@ -8246,8 +8305,7 @@ struct CalinskiHarabaszVisitor {
for (size_type i { 0 }; i < col_s; ++i) {
const long lbl { *(lbl_begin + i) };
- if (lbl == NOISE_1 || lbl == NOISE_2) [[unlikely]]
- continue;
+ if (lbl == NOISE_1 || lbl == NOISE_2) [[unlikely]] continue;
const value_type v { *(col_begin + i) };
const size_type c { label_ord(lbl) };
@@ -8283,8 +8341,7 @@ struct CalinskiHarabaszVisitor {
for (size_type i { 0 }; i < col_s; ++i) {
const long lbl { *(lbl_begin + i) };
- if (lbl == NOISE_1 || lbl == NOISE_2) [[unlikely]]
- continue;
+ if (lbl == NOISE_1 || lbl == NOISE_2) [[unlikely]] continue;
const size_type c { label_ord(lbl) };
const value_type &pt { *(col_begin + i) };
@@ -8297,10 +8354,8 @@ struct CalinskiHarabaszVisitor {
}
}
for (size_type c { 0 }; c < k; ++c)
- for (auto &v : centroids_[c])
- v /= double(counts[c]);
- for (auto &v : global_centroid_)
- v /= double(clean_cnt);
+ for (auto &v : centroids_[c]) v /= double(counts[c]);
+ for (auto &v : global_centroid_) v /= double(clean_cnt);
}
// 3. BGSS = Σᵢ nᵢ · d²(μᵢ, μ)
@@ -8319,8 +8374,7 @@ struct CalinskiHarabaszVisitor {
for (size_type i { 0 }; i < col_s; ++i) {
const long lbl { *(lbl_begin + i) };
- if (lbl == NOISE_1 || lbl == NOISE_2) [[unlikely]]
- continue;
+ if (lbl == NOISE_1 || lbl == NOISE_2) [[unlikely]] continue;
const size_type c { label_ord(lbl) };
@@ -8332,8 +8386,7 @@ struct CalinskiHarabaszVisitor {
for (size_type i { 0 }; i < col_s; ++i) {
const long lbl { *(lbl_begin + i) };
- if (lbl == NOISE_1 || lbl == NOISE_2) [[unlikely]]
- continue;
+ if (lbl == NOISE_1 || lbl == NOISE_2) [[unlikely]] continue;
const size_type c { label_ord(lbl) };
const value_type &pt { *(col_begin + i) };
@@ -8344,7 +8397,10 @@ struct CalinskiHarabaszVisitor {
sq += diff * diff;
}
- cluster_wgss_[c] += std::sqrt(sq);
+
+ // squared, matching BGSS/WGSS's own sum-of-squares definition
+ //
+ cluster_wgss_[c] += sq;
}
}
@@ -8471,7 +8527,6 @@ struct AnomalyDetectByIsoForestVisitor {
};
result_.reserve(col_s / 10 + 4);
-
if (nt_ > normalization_type::none) {
const auto &nr { norm.get_result() };
@@ -8506,11 +8561,11 @@ struct AnomalyDetectByIsoForestVisitor {
inline const scores_type &get_scores() const { return (scores_); }
explicit
- AnomalyDetectByIsoForestVisitor(long num_trees = 100,
- long max_depth = 10,
- double threshold = 0.6,
- normalization_type norm_type =
- normalization_type::none)
+ AnomalyDetectByIsoForestVisitor(
+ long num_trees = 100,
+ long max_depth = 10,
+ double threshold = 0.6,
+ normalization_type norm_type = normalization_type::none)
: num_trees_(num_trees),
max_depth_(max_depth),
threshold_(threshold),
diff --git a/include/DataFrame/Internals/DataFrame.tcc b/include/DataFrame/Internals/DataFrame.tcc
index accc5e5e..d69eb287 100644
--- a/include/DataFrame/Internals/DataFrame.tcc
+++ b/include/DataFrame/Internals/DataFrame.tcc
@@ -2100,10 +2100,14 @@ bucketize_async(bucket_type bt,
using res_t = DataFrame>;
+ // value must be captured by copy. It is a const reference parameter, so
+ // the caller can, and typically does, pass a temporary (e.g. a literal).
+ // That temporary is gone by the time this lambda runs on another thread.
+ //
return (thr_pool_.dispatch(
true,
[bt,
- &value,
+ value = V(value),
idx_visitor = std::forward(idx_visitor),
... args = std::forward(args),
this]() mutable -> res_t {
diff --git a/include/DataFrame/Internals/DataFrame_functors.h b/include/DataFrame/Internals/DataFrame_functors.h
index aa2e3c74..cd83d681 100644
--- a/include/DataFrame/Internals/DataFrame_functors.h
+++ b/include/DataFrame/Internals/DataFrame_functors.h
@@ -79,11 +79,10 @@ template
struct sort_functor_ : DataVec::template visitor_base {
inline sort_functor_ (const StlVecType &si, size_t is)
- : sorted_idxs(si), idx_s(is), done_vec(idx_s) { }
+ : sorted_idxs(si), idx_s(is) { }
const StlVecType &sorted_idxs;
const size_t idx_s;
- StlVecType done_vec;
template
void operator() (T2 &vec);
@@ -346,16 +345,16 @@ struct equal_functor_ : DataVec::template visitor_base {
template
struct mod_by_idx_functor_ : DataVec::template visitor_base {
+ using match_vec_t = StlVecType>;
+
inline mod_by_idx_functor_ (const char *n,
const DataFrame &d,
- size_type li,
- size_type ri)
- : name(n), rhs_df(d), lhs_idx(li), rhs_idx(ri) { }
+ const match_vec_t &m)
+ : name(n), rhs_df(d), matches(m) { }
- const char *name;
- const DataFrame &rhs_df;
- const size_type lhs_idx;
- const size_type rhs_idx;
+ const char *name;
+ const DataFrame &rhs_df;
+ const match_vec_t &matches; // (lhs row, rhs row) pairs
template
void operator() (T &lhs_vec) const;
@@ -707,14 +706,19 @@ struct columns_info_functor_ : DataVec::template visitor_base {
template
struct copy_remove_functor_ : DataVec::template visitor_base {
+ // del_mask[i] is non-zero if row i is to be removed. del_count is the
+ // number of such rows.
+ //
inline copy_remove_functor_ (const char *n,
- const DFUnorderedSet &td,
+ const StlVecType &dm,
+ size_type dc,
DF &d)
- : name(n), to_delete (td), df(d) { }
+ : name(n), del_mask (dm), del_count (dc), df(d) { }
- const char *name;
- const DFUnorderedSet &to_delete;
- DF &df;
+ const char *name;
+ const StlVecType &del_mask;
+ const size_type del_count;
+ DF &df;
template
void operator() (const T &vec);
diff --git a/include/DataFrame/Internals/DataFrame_misc.tcc b/include/DataFrame/Internals/DataFrame_misc.tcc
index e4e37ac6..61822573 100644
--- a/include/DataFrame/Internals/DataFrame_misc.tcc
+++ b/include/DataFrame/Internals/DataFrame_misc.tcc
@@ -41,7 +41,7 @@ template
template
template
void
-DataFrame::consistent_functor_::operator() (T &vec) const {
+DataFrame::consistent_functor_::operator()(T &vec) const {
using ValueType =
typename std::remove_reference::type::value_type;
@@ -55,7 +55,7 @@ template
template
template
void
-DataFrame::shrink_to_fit_functor_::operator() (T &vec) const {
+DataFrame::shrink_to_fit_functor_::operator()(T &vec) const {
using value_type = typename T::value_type;
@@ -73,7 +73,7 @@ template
template
template
void
-DataFrame::remove_column_functor_::operator() (T &) const {
+DataFrame::remove_column_functor_::operator()(T &) const {
using value_type = typename T::value_type;
@@ -86,9 +86,26 @@ template
template
template
void
-DataFrame::sort_functor_::operator() (T2 &vec) {
-
- _sort_by_sorted_index_(vec, sorted_idxs, done_vec, idx_s);
+DataFrame::sort_functor_::operator()(T2 &vec) {
+
+ // Gather: result[i] = vec[sorted_idxs[i]]. Random reads with sequential
+ // writes, followed by a sequential copy back, is far more cache friendly
+ // than following the permutation cycles in place (random reads AND random
+ // writes). The column's buffer is deliberately kept (not swapped out), so
+ // pointers, iterators and views into the column stay valid across sort().
+ //
+ T2 result(vec.get_allocator());
+
+ result.reserve(idx_s);
+ for (size_t i { 0 }; i < idx_s; ++i) [[likely]] {
+ auto &&item { vec[sorted_idxs[i]] };
+
+ if constexpr (std::is_same_v)
+ result.push_back(item); // std::vector proxy reference
+ else
+ result.push_back(std::move_if_noexcept(item));
+ }
+ std::move(result.begin(), result.end(), vec.begin());
}
// ----------------------------------------------------------------------------
@@ -97,7 +114,7 @@ template
template
template
void
-DataFrame::create_col_functor_::operator() (const T &) {
+DataFrame::create_col_functor_::operator()(const T &) {
using VecType = typename std::remove_reference::type;
using ValueType = typename VecType::value_type;
@@ -111,7 +128,7 @@ template
template
template
void
-DataFrame::load_functor_::operator() (const T &vec) {
+DataFrame::load_functor_::operator()(const T &vec) {
using VecType = typename std::remove_reference::type;
using ValueType = typename VecType::value_type;
@@ -132,7 +149,7 @@ template
template
template
void
-DataFrame::load_all_functor_::operator() (const T &vec) {
+DataFrame::load_all_functor_::operator()(const T &vec) {
using VecType = typename std::remove_reference::type;
using ValueType = typename VecType::value_type;
@@ -149,7 +166,7 @@ template
template
template
void
-DataFrame::remove_functor_::operator() (T &vec) {
+DataFrame::remove_functor_::operator()(T &vec) {
vec.erase(vec.begin() + begin, vec.begin() + end);
}
@@ -160,7 +177,7 @@ template
template
template
void
-DataFrame::truncate_functor_::operator() (T &vec) {
+DataFrame::truncate_functor_::operator()(T &vec) {
if (after < vec.size())
vec.erase(vec.begin() + after, vec.end());
@@ -175,7 +192,7 @@ template
template
void
DataFrame::view_setup_functor_::
-operator() (T &vec) {
+operator()(T &vec) {
using VecType = typename std::remove_reference::type;
using ValueType = typename VecType::value_type;
@@ -201,7 +218,7 @@ template
template
template
void
-DataFrame::add_col_functor_::operator() (const T &) {
+DataFrame::add_col_functor_::operator()(const T &) {
using VecType = typename std::remove_reference::type;
using ValueType = typename VecType::value_type;
@@ -216,21 +233,20 @@ template
template
template
void
-DataFrame::print_csv_functor_::operator() (const T &vec) {
+DataFrame::print_csv_functor_::operator()(const T &vec) {
if (vec.empty()) return;
using VecType = typename std::remove_reference::type;
using ValueType = typename VecType::value_type;
- _write_csv_df_header_(os, name, vec.size())
- << ':';
+ const long vec_size { long(vec.size()) };
+ const long sr { long(std::min(start_row, vec_size)) };
+ const long er { long(std::min(end_row, vec_size)) };
- const long vec_size = vec.size();
- const long sr = std::min(start_row, vec_size);
- const long er = std::min(end_row, vec_size);
+ _write_csv_df_header_(os, name, er - sr) << ':';
- for (long i = sr; i < er; ++i)
+ for (long i { sr }; i < er; ++i)
_write_csv_df_index_(os, vec[i]) << delim;
os << '\n';
@@ -243,18 +259,20 @@ template
template
template
void
-DataFrame::print_binary_functor_::operator() (const T &vec) {
+DataFrame::print_binary_functor_::operator()(const T &vec) {
using VecType = typename std::remove_reference::type;
using ValueType = typename VecType::value_type;
- char col_name[MAX_COL_NAME_SIZE];
+ // Zero-filled, so the last byte written is never garbage
+ //
+ char col_name[MAX_COL_NAME_SIZE] { };
std::strncpy(col_name, name, sizeof(col_name) - 1);
os.write(col_name, sizeof(col_name));
- const long local_start_row = std::min (long(vec.size()), start_row);
- const long local_end_row = std::min (long(vec.size()), end_row);
+ const long local_start_row { std::min (long(vec.size()), start_row) };
+ const long local_end_row { std::min (long(vec.size()), end_row) };
if constexpr (std::is_same_v ||
std::is_same_v ||
@@ -303,7 +321,7 @@ template
template
template
void
-DataFrame::print_json_functor_::operator() (const T &vec) {
+DataFrame::print_json_functor_::operator()(const T &vec) {
if (vec.empty()) return;
@@ -313,16 +331,16 @@ DataFrame::print_json_functor_::operator() (const T &vec) {
if (need_pre_comma)
os << ",\n";
- _write_json_df_header_(os, name, vec.size());
+ const long vec_size { long(vec.size()) };
+ const long sr { long(std::min(start_row, vec_size)) };
+ const long er { long(std::min(end_row, vec_size)) };
- const long vec_size = vec.size();
- const long sr = std::min(start_row, vec_size);
- const long er = std::min(end_row, vec_size);
+ _write_json_df_header_(os, name, er - sr);
os << "\"D\":[";
- if (vec_size > 0) {
- _write_json_df_index_(os, vec[start_row]);
- for (long i = sr + 1; i < er; ++i) {
+ if (er > sr) {
+ _write_json_df_index_(os, vec[sr]);
+ for (long i { sr + 1 }; i < er; ++i) {
os << ',';
_write_json_df_index_(os, vec[i]);
}
@@ -338,7 +356,7 @@ template
template
template
void DataFrame::
-print_csv2_header_functor_::operator() (const T &vec) {
+print_csv2_header_functor_::operator()(const T &vec) {
using VecType = typename std::remove_reference::type;
using ValueType = typename VecType::value_type;
@@ -359,7 +377,7 @@ template
template
template
void DataFrame::
-print_csv2_data_functor_::operator() (const T &vec) {
+print_csv2_data_functor_::operator()(const T &vec) {
using VecType = typename std::remove_reference::type;
using ValueType = typename VecType::value_type;
@@ -379,7 +397,7 @@ template
template
void
DataFrame::
-equal_functor_::operator() (const T &lhs_vec) {
+equal_functor_::operator()(const T &lhs_vec) {
using VecType = typename std::remove_reference::type;
using ValueType = typename VecType::value_type;
@@ -405,18 +423,27 @@ template
template
void
DataFrame::
-mod_by_idx_functor_::operator() (T &lhs_vec) const {
+mod_by_idx_functor_::operator()(T &lhs_vec) const {
using VecType = typename std::remove_reference::type;
using ValueType = typename VecType::value_type;
- const auto &iter = rhs_df.column_tb_.find(name);
+ const auto &iter { rhs_df.column_tb_.find(name) };
if (iter != rhs_df.column_tb_.end()) {
- const ColumnVecType &rhs_vec =
- rhs_df.get_column(name, false);
-
- lhs_vec[lhs_idx] = rhs_vec[rhs_idx];
+ const ColumnVecType &rhs_vec {
+ rhs_df.get_column(name, false)
+ };
+ const size_type lhs_s { lhs_vec.size() };
+ const size_type rhs_s { rhs_vec.size() };
+
+ // The column lookup is done once per column, not once per row. A
+ // column could be shorter than the index.
+ //
+ for (const auto &[lhs_i, rhs_i] : matches) {
+ if (lhs_i < lhs_s && rhs_i < rhs_s)
+ lhs_vec[lhs_i] = rhs_vec[rhs_i];
+ }
}
}
@@ -427,7 +454,7 @@ template
template
void
DataFrame::
-create_join_common_col_functor_::operator() (const T &) {
+create_join_common_col_functor_::operator()(const T &) {
using VecType = typename std::remove_reference::type;
using ValueType = typename VecType::value_type;
@@ -523,23 +550,25 @@ template
void
DataFrame::
concat_load_view_functor_::
-operator() (const T &vec) {
+operator()(const T &vec) {
using VecType = typename std::remove_reference::type;
using ValueType = typename VecType::value_type;
- const size_type rhs_s = vec.size();
- const bool has_col = result.has_column(name);
+ const size_type rhs_s { vec.size() };
+ const bool has_col { result.has_column(name) };
if (has_col) {
- auto &result_vec =
- result.template get_column(name);
- const size_type min_s =
- std::min(result_vec.size() + rhs_s, result.get_index().size());
- size_type count { vec.size() };
+ auto &result_vec {
+ result.template get_column(name)
+ };
+ const size_type min_s {
+ std::min(result_vec.size() + rhs_s, result.get_index().size())
+ };
+ size_type count { result_vec.size() };
result_vec.reserve(min_s);
- for (size_type i = 0; i < rhs_s && count < min_s; ++i, ++count)
+ for (size_type i { 0 }; i < rhs_s && count < min_s; ++i, ++count)
result_vec.push_back(const_cast(&(vec[i])));
}
else {
@@ -549,7 +578,6 @@ operator() (const T &vec) {
typename VecType::iterator>(
name, { nc_vec.begin(), nc_vec.end() });
}
-
}
// ----------------------------------------------------------------------------
@@ -589,7 +617,7 @@ template
template
template
void DataFrame::vertical_shift_functor_::
-operator() (T &vec) const {
+operator()(T &vec) const {
if (sp == shift_policy::up)
shift_left(vec, n);
@@ -603,7 +631,7 @@ template
template
template
void DataFrame::rotate_functor_::
-operator() (T &vec) const {
+operator()(T &vec) const {
if (sp == shift_policy::up) // Rotate left
// There is no checking the value of n
@@ -626,21 +654,26 @@ operator()(const T &lhs_vec) {
using VecType = typename std::remove_reference::type;
using ValueType = typename VecType::value_type;
- const auto rhs_citer = rhs_df.column_tb_.find(col_name);
+ const auto rhs_citer { rhs_df.column_tb_.find(col_name) };
if (rhs_citer == rhs_df.column_tb_.end()) return;
- const DataVec &rhs_hv = rhs_df.data_[rhs_citer->second];
- const auto &rhs_vec = rhs_hv.template get_vector();
- const size_type new_col_size =
- std::min(std::min(lhs_vec.size(), rhs_vec.size()), new_idx.size());
+ const DataVec &rhs_hv { rhs_df.data_[rhs_citer->second] };
+ const auto &rhs_vec {
+ rhs_hv.template get_vector()
+ };
+ const size_type new_col_size {
+ std::min(std::min(lhs_vec.size(), rhs_vec.size()), new_idx.size())
+ };
StlVecType new_col;
- auto opt = OPT { };
- size_type lcounter = 0;
- size_type rcounter = 0;
+ auto opt = OPT{ };
+ size_type lcounter { 0 };
+ size_type rcounter { 0 };
new_col.reserve(new_col_size);
- for (size_type idx = 0; idx < new_col_size; ) {
+ for (size_type idx { 0 }; idx < new_col_size &&
+ lcounter < lhs_vec.size() &&
+ rcounter < rhs_vec.size(); ) {
if (lhs_idx[lcounter] == rhs_idx[rcounter]) {
new_col.push_back(opt(lhs_vec[lcounter], rhs_vec[rcounter]));
lcounter += 1;
@@ -759,7 +792,7 @@ template
void
DataFrame::
sel_load_functor_