cxtream  0.5.1
C++17 data pipeline with Python bindings.
dataframe.hpp
1 /****************************************************************************
2  * cxtream library
3  * Copyright (c) 2017, Cognexa Solutions s.r.o.
4  * Author(s) Filip Matzner
5  *
6  * This file is distributed under the MIT License.
7  * See the accompanying file LICENSE.txt for the complete license agreement.
8  ****************************************************************************/
10 
11 #ifndef CXTREAM_CORE_DATAFRAME_HPP
12 #define CXTREAM_CORE_DATAFRAME_HPP
13 
14 #include <cxtream/core/index_mapper.hpp>
15 #include <cxtream/core/utility/string.hpp>
16 #include <cxtream/core/utility/tuple.hpp>
17 
18 #include <range/v3/experimental/view/shared.hpp>
19 #include <range/v3/view/all.hpp>
20 #include <range/v3/view/iota.hpp>
21 #include <range/v3/view/move.hpp>
22 #include <range/v3/view/transform.hpp>
23 #include <range/v3/view/zip.hpp>
24 
25 #include <functional>
26 #include <iomanip>
27 #include <iostream>
28 #include <vector>
29 
30 namespace cxtream {
31 
37 template<typename DataTable = std::vector<std::vector<std::string>>>
38 class dataframe {
39 public:
40  dataframe() = default;
41 
59  template<typename T>
60  dataframe(std::vector<std::vector<T>> columns, std::vector<std::string> header = {})
61  {
62  throw_check_new_header(columns.size(), header);
63  for (std::size_t i = 0; i < columns.size(); ++i) {
64  std::string col_name = header.empty() ? "" : std::move(header[i]);
65  insert_col(columns[i] | ranges::view::move, std::move(col_name));
66  }
67  }
68 
90  template<typename... Ts>
91  dataframe(std::tuple<std::vector<Ts>...> columns, std::vector<std::string> header = {})
92  {
93  throw_check_new_header(sizeof...(Ts), header);
94  utility::tuple_for_each_with_index(std::move(columns),
95  [this, &header](auto& column, auto index) {
96  std::string col_name = header.empty() ? "" : std::move(header[index]);
97  this->insert_col(column | ranges::view::move, std::move(col_name));
98  });
99  }
100 
101  // insertion //
102 
113  template<typename Rng, typename ValueT = ranges::range_value_type_t<Rng>>
114  std::size_t insert_col(Rng&& rng, std::string col_name = {},
115  std::function<std::string(const ValueT&)> cvt =
116  static_cast<std::string (*)(const ValueT&)>(utility::to_string))
117  {
118  throw_check_insert_col_name(col_name);
119  throw_check_insert_col_size(ranges::size(rng));
120  if (col_name.size()) header_.insert(col_name);
121  data_.emplace_back(rng | ranges::view::transform(cvt));
122  return n_cols() - 1;
123  }
124 
134  template<typename... Ts>
135  std::size_t insert_row(std::tuple<Ts...> row_tuple,
136  std::tuple<std::function<std::string(const Ts&)>...> cvts =
137  std::make_tuple(
138  static_cast<std::string (*)(const Ts&)>(utility::to_string)...))
139  {
140  throw_check_insert_row_size(sizeof...(Ts));
141  utility::tuple_for_each_with_index(std::move(row_tuple),
142  [this, &cvts](auto& field, auto index) {
143  this->data_.at(index).push_back(std::get<index>(cvts)(std::move(field)));
144  });
145  return n_rows() - 1;
146  }
147 
157  std::size_t insert_row(std::vector<std::string> row)
158  {
159  throw_check_insert_row_size(row.size());
160  for (std::size_t i = 0; i < n_cols(); ++i) {
161  data_[i].push_back(std::move(row[i]));
162  }
163  return n_rows() - 1;
164  }
165 
166  // drop //
167 
171  void drop_icol(std::size_t col_index)
172  {
173  throw_check_col_idx(col_index);
174  // remove the column from the header
175  if (header_.size()) {
176  std::vector<std::string> new_header = header_.values();
177  new_header.erase(new_header.begin() + col_index);
178  header_ = new_header;
179  }
180  // remove the column from the data
181  data_.erase(data_.begin() + col_index);
182  }
183 
187  void drop_col(const std::string& col_name)
188  {
189  throw_check_col_name(col_name);
190  return drop_icol(header_.index_for(col_name));
191  }
192 
196  void drop_row(const std::size_t row_idx)
197  {
198  throw_check_row_idx(row_idx);
199  for (auto& column : data_) {
200  column.erase(column.begin() + row_idx);
201  }
202  }
203 
204  // raw column access //
205 
217  auto raw_icol(std::size_t col_index)
218  {
219  throw_check_col_idx(col_index);
220  return raw_cols()[col_index] | ranges::view::all;
221  }
222 
227  auto raw_icol(std::size_t col_index) const
228  {
229  throw_check_col_idx(col_index);
230  return raw_cols()[col_index] | ranges::view::all;
231  }
232 
244  auto raw_col(const std::string& col_name)
245  {
246  throw_check_col_name(col_name);
247  return raw_icol(header_.index_for(col_name));
248  }
249 
256  auto raw_col(const std::string& col_name) const
257  {
258  throw_check_col_name(col_name);
259  return raw_icol(header_.index_for(col_name));
260  }
261 
262  // typed column access //
263 
276  template<typename T>
277  auto icol(std::size_t col_index,
278  std::function<T(const std::string&)> cvt = utility::string_to<T>) const
279  {
280  return raw_icol(col_index) | ranges::view::transform(cvt);
281  }
282 
295  template<typename T>
296  auto col(const std::string& col_name,
297  std::function<T(const std::string&)> cvt = utility::string_to<T>) const
298  {
299  throw_check_col_name(col_name);
300  return icol<T>(header_.index_for(col_name), std::move(cvt));
301  }
302 
303  // raw multi column access //
304 
316  auto raw_cols()
317  {
318  return data_ | ranges::view::transform(ranges::view::all);
319  }
320 
326  auto raw_cols() const
327  {
328  return data_ | ranges::view::transform(ranges::view::all);
329  }
330 
343  auto raw_icols(std::vector<std::size_t> col_indexes)
344  {
345  for (auto& col_idx : col_indexes) throw_check_col_idx(col_idx);
346  return raw_icols_impl(this, std::move(col_indexes));
347  }
348 
355  auto raw_icols(std::vector<std::size_t> col_indexes) const
356  {
357  for (auto& col_idx : col_indexes) throw_check_col_idx(col_idx);
358  return raw_icols_impl(this, std::move(col_indexes));
359  }
360 
373  auto raw_cols(const std::vector<std::string>& col_names)
374  {
375  for (auto& col_name : col_names) throw_check_col_name(col_name);
376  return raw_icols(header_.index_for(col_names));
377  }
378 
385  auto raw_cols(const std::vector<std::string>& col_names) const
386  {
387  for (auto& col_name : col_names) throw_check_col_name(col_name);
388  return raw_icols(header_.index_for(col_names));
389  }
390 
391  // typed multi column access //
392 
402  template<typename... Ts>
403  auto icols(std::vector<std::size_t> col_indexes,
404  std::tuple<std::function<Ts(const std::string&)>...> cvts =
405  std::make_tuple(utility::string_to<Ts>...)) const
406  {
407  assert(sizeof...(Ts) == ranges::size(col_indexes));
408  return utility::tuple_transform_with_index(std::move(cvts),
409  [raw_cols = raw_icols(std::move(col_indexes))](auto&& cvt, auto i) {
410  return raw_cols[i] | ranges::view::transform(std::move(cvt));
411  });
412  }
413 
424  template<typename... Ts>
425  auto cols(const std::vector<std::string>& col_names,
426  std::tuple<std::function<Ts(const std::string&)>...> cvts =
427  std::make_tuple(utility::string_to<Ts>...)) const
428  {
429  for (auto& col_name : col_names) throw_check_col_name(col_name);
430  return icols<Ts...>(header_.index_for(col_names), std::move(cvts));
431  }
432 
442  auto raw_rows()
443  {
444  return raw_rows_impl(this);
445  }
446 
452  auto raw_rows() const
453  {
454  return raw_rows_impl(this);
455  }
456 
467  auto raw_irows(std::vector<std::size_t> col_indexes)
468  {
469  for (auto& col_idx : col_indexes) throw_check_col_idx(col_idx);
470  return raw_irows_impl(this, std::move(col_indexes));
471  }
472 
479  auto raw_irows(std::vector<std::size_t> col_indexes) const
480  {
481  for (auto& col_idx : col_indexes) throw_check_col_idx(col_idx);
482  return raw_irows_impl(this, std::move(col_indexes));
483  }
484 
495  auto raw_rows(const std::vector<std::string>& col_names)
496  {
497  for (auto& col_name : col_names) throw_check_col_name(col_name);
498  return raw_irows(header_.index_for(col_names));
499  }
500 
507  auto raw_rows(const std::vector<std::string>& col_names) const
508  {
509  for (auto& col_name : col_names) throw_check_col_name(col_name);
510  return raw_irows(header_.index_for(col_names));
511  }
512 
513  // typed row access //
514 
527  template<typename... Ts>
528  auto irows(std::vector<std::size_t> col_indexes,
529  std::tuple<std::function<Ts(const std::string&)>...> cvts =
530  std::make_tuple(utility::string_to<Ts>...)) const
531  {
532  return std::experimental::apply(
533  ranges::view::zip,
534  icols<Ts...>(std::move(col_indexes), std::move(cvts)));
535  }
536 
549  template<typename... Ts>
550  auto rows(const std::vector<std::string>& col_names,
551  std::tuple<std::function<Ts(const std::string&)>...> cvts =
552  std::make_tuple(utility::string_to<Ts>...)) const
553  {
554  for (auto& col_name : col_names) throw_check_col_name(col_name);
555  return irows<Ts...>(header_.index_for(col_names), std::move(cvts));
556  }
557 
558  // typed indexed single column access //
559 
577  template <typename IndexT, typename ColT>
578  auto index_icol(std::size_t key_col_index,
579  std::size_t val_col_index,
580  std::function<IndexT(const std::string&)> key_col_cvt =
581  utility::string_to<IndexT>,
582  std::function<ColT(const std::string&)> val_col_cvt =
583  utility::string_to<ColT>) const
584  {
585  auto key_col = icol<IndexT>(key_col_index, std::move(key_col_cvt));
586  auto val_col = icol<ColT>(val_col_index, std::move(val_col_cvt));
587  return ranges::view::zip(key_col, val_col);
588  }
589 
598  template<typename IndexT, typename ColT>
599  auto index_col(const std::string& key_col_name,
600  const std::string& val_col_name,
601  std::function<IndexT(const std::string&)> key_col_cvt =
602  utility::string_to<IndexT>,
603  std::function<ColT(const std::string&)> val_col_cvt =
604  utility::string_to<ColT>) const
605  {
606  throw_check_col_name(key_col_name);
607  throw_check_col_name(val_col_name);
608  return index_icol(header_.index_for(key_col_name),
609  header_.index_for(val_col_name),
610  std::move(key_col_cvt),
611  std::move(val_col_cvt));
612  }
613 
614  // typed indexed multiple column access //
615 
627  template<typename IndexT, typename... Ts>
628  auto index_icols(std::size_t key_col_index,
629  std::vector<std::size_t> val_col_indexes,
630  std::function<IndexT(const std::string&)> key_col_cvt =
631  utility::string_to<IndexT>,
632  std::tuple<std::function<Ts(const std::string&)>...> val_col_cvts =
633  std::make_tuple(utility::string_to<Ts>...)) const
634  {
635  auto key_col = icol<IndexT>(key_col_index, std::move(key_col_cvt));
636  auto val_cols = irows<Ts...>(std::move(val_col_indexes), std::move(val_col_cvts));
637  return ranges::view::zip(key_col, val_cols);
638  }
639 
651  template<typename IndexT, typename... Ts>
652  auto index_cols(const std::string& key_col_name,
653  const std::vector<std::string>& val_col_names,
654  std::function<IndexT(const std::string&)> key_col_cvt =
655  utility::string_to<IndexT>,
656  std::tuple<std::function<Ts(const std::string&)>...> val_col_cvts =
657  std::make_tuple(utility::string_to<Ts>...)) const
658  {
659  throw_check_col_name(key_col_name);
660  for (auto& col_name : val_col_names) throw_check_col_name(col_name);
661  assert(header_.size() && "Dataframe has no header, cannot index by column name.");
662  return index_icols(header_.index_for(key_col_name),
663  header_.index_for(val_col_names),
664  std::move(key_col_cvt),
665  std::move(val_col_cvts));
666  }
667 
668  // shape functions //
669 
671  std::size_t n_cols() const
672  {
673  return data_.size();
674  }
675 
677  std::size_t n_rows() const
678  {
679  if (n_cols() == 0) return 0;
680  return data_.front().size();
681  }
682 
687  void header(std::vector<std::string> new_header)
688  {
689  throw_check_new_header(n_cols(), new_header);
690  header_ = std::move(new_header);
691  }
692 
694  std::vector<std::string> header() const
695  {
696  return header_.values();
697  }
698 
700  DataTable& data()
701  {
702  return data_;
703  }
704 
706  const DataTable& data() const
707  {
708  return data_;
709  }
710 
711 private:
712 
713  static void throw_check_new_header(
714  std::size_t n_cols,
715  const std::vector<std::string>& header)
716  {
717  if (header.size() && header.size() != n_cols) {
718  throw std::invalid_argument{"The dataframe with " + std::to_string(n_cols) +
719  " columns cannot have a header of size " + std::to_string(header.size()) + "."};
720  }
721  for (const std::string& h : header) {
722  if (!h.size()) {
723  throw std::invalid_argument{"When providing a header to a dataframe,"
724  " all the column names have to be non-empty."};
725  }
726  }
727  }
728 
729  void throw_check_insert_col_name(const std::string& name) const
730  {
731  if (header_.size() && !name.size()) {
732  throw std::invalid_argument{"The dataframe has a header, please provide"
733  " a column name when inserting a new column."};
734  }
735  if (n_cols() != 0 && !header_.size() && name.size()) {
736  throw std::invalid_argument{"The dataframe has no header, but a column"
737  " name \"" + name + "\" was provided when inserting a new column."};
738  }
739  }
740 
741  void throw_check_insert_col_size(std::size_t col_size) const
742  {
743  if (n_rows() != 0 && col_size != n_rows()) {
744  throw std::invalid_argument{"Cannot insert a column of size "
745  + std::to_string(col_size) + " to a dataframe with "
746  + std::to_string(n_rows()) + " rows."};
747  }
748  }
749 
750  void throw_check_insert_row_size(std::size_t row_size) const
751  {
752  if (n_cols() != 0 && row_size != n_cols()) {
753  throw std::invalid_argument{"Cannot insert a row of size "
754  + std::to_string(row_size) + " to a dataframe with "
755  + std::to_string(n_cols()) + " columns."};
756  }
757  }
758 
759  void throw_check_row_idx(std::size_t row_idx) const
760  {
761  if (row_idx < 0 || row_idx >= n_rows()) {
762  throw std::out_of_range{"Row index " + std::to_string(row_idx) +
763  " is not in a dataframe with " + std::to_string(n_rows()) + " rows."};
764  }
765  }
766 
767  void throw_check_col_idx(std::size_t col_idx) const
768  {
769  if (col_idx < 0 || col_idx >= n_cols()) {
770  throw std::out_of_range{"Column index " + std::to_string(col_idx) +
771  " is not in a dataframe with " + std::to_string(n_cols()) + " columns."};
772  }
773  }
774 
775  void throw_check_col_name(const std::string& col_name) const
776  {
777  if (header_.size() == 0) {
778  throw std::out_of_range{"Dataframe has no header, cannot index by column name."};
779  }
780  if (!header_.contains(col_name)) {
781  throw std::out_of_range{"Column " + col_name + " not found in the dataframe."};
782  }
783  }
784 
785  template <typename This>
786  static auto raw_irows_impl(This this_ptr, std::vector<std::size_t> col_indexes)
787  {
788  namespace view = ranges::view;
789  return view::iota(0UL, this_ptr->n_rows())
790  | view::transform([this_ptr, col_indexes=std::move(col_indexes)](std::size_t i) {
791  return this_ptr->raw_icols(col_indexes)
792  // decltype(auto) to make sure a reference is returned
793  | view::transform([i](auto&& col) -> decltype(auto) {
794  return col[i];
795  });
796  });
797  }
798 
799  template<typename This>
800  static auto raw_rows_impl(This this_ptr)
801  {
802  namespace view = ranges::view;
803  return view::iota(0UL, this_ptr->n_rows())
804  | view::transform([this_ptr](std::size_t i) {
805  return view::iota(0UL, this_ptr->n_cols())
806  // decltype(auto) to make sure a reference is returned
807  | view::transform([this_ptr, i](std::size_t j) -> decltype(auto) {
808  return this_ptr->raw_cols()[j][i];
809  });
810  });
811  }
812 
813  template<typename This>
814  static auto raw_icols_impl(This this_ptr, std::vector<std::size_t> col_indexes)
815  {
816  return std::move(col_indexes)
817  | ranges::experimental::view::shared
818  | ranges::view::transform([this_ptr](std::size_t idx) {
819  return this_ptr->raw_cols()[idx];
820  });
821  }
822 
823 
824  // data storage //
825 
826  DataTable data_;
827 
828  using header_t = index_mapper<std::string>;
829  header_t header_;
830 
831 }; // class dataframe
832 
835 template<typename DataTable>
836 std::ostream& operator<<(std::ostream& out, const dataframe<DataTable>& df)
837 {
838  namespace view = ranges::view;
839  // calculate the width of the columns using their longest field
840  std::vector<std::size_t> col_widths = df.raw_cols()
841  | view::transform([](auto&& col) {
842  std::vector<std::size_t> elem_sizes = col
843  | view::transform([](auto& field) { return ranges::size(field); });
844  return ranges::max(elem_sizes) + 2;
845  });
846 
847  auto header = df.header();
848  if (header.size()) {
849  // update col_widths using header widths
850  col_widths = view::zip(col_widths, header)
851  | view::transform([](auto&& tpl) {
852  return std::max(std::get<0>(tpl), std::get<1>(tpl).size() + 2);
853  });
854 
855  // print header
856  for (std::size_t j = 0; j < header.size(); ++j) {
857  out << std::setw(col_widths[j]) << header[j];
858  if (j + 1 < header.size()) out << '|';
859  else out << '\n';
860  }
861 
862  // print header and data separator
863  for (std::size_t j = 0; j < header.size(); ++j) {
864  out << std::setw(col_widths[j]) << std::setfill('-');
865  if (j + 1 < header.size()) out << '-' << '+';
866  else out << '-' << '\n';
867  }
868  out << std::setfill(' ');
869  }
870 
871  // print data
872  for (std::size_t i = 0; i < df.n_rows(); ++i) {
873  for (std::size_t j = 0; j < df.n_cols(); ++j) {
874  out << std::setw(col_widths[j]) << df.raw_rows()[i][j];
875  if (j + 1 < df.n_cols()) out << '|';
876  else out << '\n';
877  }
878  }
879 
880  return out;
881 }
882 
883 } // end namespace cxtream
884 #endif
auto raw_cols(const std::vector< std::string > &col_names)
Definition: dataframe.hpp:373
constexpr auto tuple_transform_with_index(Tuple &&tuple, Fun &&fun)
Similar to tuple_transform(), but with index available.
Definition: tuple.hpp:641
auto raw_icol(std::size_t col_index)
Definition: dataframe.hpp:217
std::size_t n_rows() const
Return the number of rows (excluding header).
Definition: dataframe.hpp:677
constexpr auto transform(from_t< FromColumns... > f, to_t< ToColumns... > t, Fun fun, dim_t< Dim > d=dim_t< 1 >{})
Transform a subset of cxtream columns to a different subset of cxtream columns.
Definition: transform.hpp:177
dataframe(std::vector< std::vector< T >> columns, std::vector< std::string > header={})
Definition: dataframe.hpp:60
auto raw_icols(std::vector< std::size_t > col_indexes) const
Definition: dataframe.hpp:355
auto raw_icols(std::vector< std::size_t > col_indexes)
Definition: dataframe.hpp:343
auto index_icols(std::size_t key_col_index, std::vector< std::size_t > val_col_indexes, std::function< IndexT(const std::string &)> key_col_cvt=utility::string_to< IndexT >, std::tuple< std::function< Ts(const std::string &)>... > val_col_cvts=std::make_tuple(utility::string_to< Ts >...)) const
Definition: dataframe.hpp:628
std::size_t size() const
Returns the size of the mapper.
auto raw_icol(std::size_t col_index) const
Definition: dataframe.hpp:227
std::size_t index_for(const T &val) const
auto rows(const std::vector< std::string > &col_names, std::tuple< std::function< Ts(const std::string &)>... > cvts=std::make_tuple(utility::string_to< Ts >...)) const
Definition: dataframe.hpp:550
auto raw_rows() const
Definition: dataframe.hpp:452
auto index_col(const std::string &key_col_name, const std::string &val_col_name, std::function< IndexT(const std::string &)> key_col_cvt=utility::string_to< IndexT >, std::function< ColT(const std::string &)> val_col_cvt=utility::string_to< ColT >) const
Definition: dataframe.hpp:599
std::size_t insert_row(std::tuple< Ts... > row_tuple, std::tuple< std::function< std::string(const Ts &)>... > cvts=std::make_tuple(static_cast< std::string(*)(const Ts &)>(utility::to_string)...))
Definition: dataframe.hpp:135
void drop_col(const std::string &col_name)
Definition: dataframe.hpp:187
void drop_icol(std::size_t col_index)
Definition: dataframe.hpp:171
std::string to_string(const T &value)
Convert the given type to std::string.
Definition: string.hpp:91
auto col(const std::string &col_name, std::function< T(const std::string &)> cvt=utility::string_to< T >) const
Definition: dataframe.hpp:296
std::vector< std::string > header() const
Return the names of columns.
Definition: dataframe.hpp:694
constexpr auto tuple_for_each_with_index(Tuple &&tuple, Fun &&fun)
Similar to tuple_for_each(), but with index available.
Definition: tuple.hpp:605
auto raw_cols(const std::vector< std::string > &col_names) const
Definition: dataframe.hpp:385
Tabular object with convenient data access methods.
Definition: dataframe.hpp:38
bool contains(const T &val) const
Checks whether the mapper contains the given value.
auto raw_col(const std::string &col_name)
Definition: dataframe.hpp:244
dataframe(std::tuple< std::vector< Ts >... > columns, std::vector< std::string > header={})
Definition: dataframe.hpp:91
auto raw_col(const std::string &col_name) const
Definition: dataframe.hpp:256
auto raw_irows(std::vector< std::size_t > col_indexes) const
Definition: dataframe.hpp:479
auto raw_rows(const std::vector< std::string > &col_names)
Definition: dataframe.hpp:495
const DataTable & data() const
Return a const reference to the raw data table.
Definition: dataframe.hpp:706
auto index_cols(const std::string &key_col_name, const std::vector< std::string > &val_col_names, std::function< IndexT(const std::string &)> key_col_cvt=utility::string_to< IndexT >, std::tuple< std::function< Ts(const std::string &)>... > val_col_cvts=std::make_tuple(utility::string_to< Ts >...)) const
Definition: dataframe.hpp:652
auto irows(std::vector< std::size_t > col_indexes, std::tuple< std::function< Ts(const std::string &)>... > cvts=std::make_tuple(utility::string_to< Ts >...)) const
Definition: dataframe.hpp:528
std::size_t insert_row(std::vector< std::string > row)
Definition: dataframe.hpp:157
std::size_t n_cols() const
Return the number of columns.
Definition: dataframe.hpp:671
auto icol(std::size_t col_index, std::function< T(const std::string &)> cvt=utility::string_to< T >) const
Definition: dataframe.hpp:277
std::size_t insert(T val)
auto raw_cols() const
Definition: dataframe.hpp:326
void drop_row(const std::size_t row_idx)
Definition: dataframe.hpp:196
DataTable & data()
Return a reference to the raw data table.
Definition: dataframe.hpp:700
const std::vector< T > & values() const
Returns all the contained values.
auto raw_rows(const std::vector< std::string > &col_names) const
Definition: dataframe.hpp:507
auto index_icol(std::size_t key_col_index, std::size_t val_col_index, std::function< IndexT(const std::string &)> key_col_cvt=utility::string_to< IndexT >, std::function< ColT(const std::string &)> val_col_cvt=utility::string_to< ColT >) const
Definition: dataframe.hpp:578
auto icols(std::vector< std::size_t > col_indexes, std::tuple< std::function< Ts(const std::string &)>... > cvts=std::make_tuple(utility::string_to< Ts >...)) const
Definition: dataframe.hpp:403
auto raw_irows(std::vector< std::size_t > col_indexes)
Definition: dataframe.hpp:467
void header(std::vector< std::string > new_header)
Definition: dataframe.hpp:687
std::size_t insert_col(Rng &&rng, std::string col_name={}, std::function< std::string(const ValueT &)> cvt=static_cast< std::string(*)(const ValueT &)>(utility::to_string))
Definition: dataframe.hpp:114
auto cols(const std::vector< std::string > &col_names, std::tuple< std::function< Ts(const std::string &)>... > cvts=std::make_tuple(utility::string_to< Ts >...)) const
Definition: dataframe.hpp:425