Vince's CSV Parser
Loading...
Searching...
No Matches
data_frame.hpp
1#pragma once
2
3#include <algorithm>
4#include <cstdint>
5#include <iterator>
6#include <memory>
7#include <sstream>
8#include <stdexcept>
9#include <string>
10#include <type_traits>
11#include <unordered_map>
12#include <utility>
13#include <vector>
14
15#include "../csv_exceptions.hpp"
16#include "../csv_reader.hpp"
17#include "../csv_writer.hpp"
18#include "../json_converter.hpp"
19#include "../raw_csv_data.hpp"
20#include "data_frame_column.hpp"
21#include "data_frame_executor.hpp"
22#include "data_frame_options.hpp"
23#include "data_frame_row.hpp"
24#include "data_frame_storage.hpp"
25#include "fwd.hpp"
26#include "key_storage.hpp"
27#include "row_overlay.hpp"
28
29namespace csv {
30 template<typename KeyType = std::string>
31 class DataFrame {
32 public:
33 friend class DataFrameRow<KeyType>;
34 friend class DataFrameColumn<KeyType>;
37
39 DataFrameRow<KeyType> operator()(DataFrame<KeyType>* owner, size_t row_index) const {
40 return owner->make_row_proxy(row_index);
41 }
42 };
43
45 DataFrameRow<KeyType> operator()(const DataFrame<KeyType>* owner, size_t row_index) const {
46 return owner->make_const_row_proxy(row_index);
47 }
48 };
49
52
55
56 static_assert(
58 "DataFrame<KeyType> requires KeyType to be hashable (std::hash<KeyType> specialization required)."
59 );
60
61 static_assert(
63 "DataFrame<KeyType> requires KeyType to be equality comparable (operator== required)."
64 );
65
66 static_assert(
67 std::is_default_constructible<KeyType>::value,
68 "DataFrame<KeyType> requires KeyType to be default-constructible."
69 );
70
72
74 DataFrame() = default;
75
80#if CSV_EXCEPTIONS_ENABLED
81 explicit DataFrame(CSVReader& reader) {
82 CSVError error;
83 this->init_unkeyed_from_reader(reader, error);
84 internals::throw_dataframe_error(error);
85 }
86#endif
87
92#if CSV_EXCEPTIONS_ENABLED
93 explicit DataFrame(
94 csv::string_view filename,
96 ) {
97 CSVReader reader(filename, format);
98 CSVError error;
99 this->init_unkeyed_from_reader(reader, error);
100 internals::throw_dataframe_error(error);
101 }
102#endif
103
105 explicit DataFrame(std::vector<CSVRow> rows) {
106 this->init_unkeyed_from_rows(rows);
107 }
108
113#if CSV_EXCEPTIONS_ENABLED
114 explicit DataFrame(CSVReader& reader, const DataFrameOptions& options) {
115 CSVError error;
116 this->init_from_reader(reader, options, error);
117 internals::throw_dataframe_error(error);
118 }
119#endif
120
125#if CSV_EXCEPTIONS_ENABLED
127 csv::string_view filename,
128 const DataFrameOptions& options,
130 ) {
131 CSVReader reader(filename, format);
132 CSVError error;
133 this->init_from_reader(reader, options, error);
134 internals::throw_dataframe_error(error);
135 }
136#endif
137
142#if CSV_EXCEPTIONS_ENABLED
144 CSVReader& reader,
145 const std::string& _key_column,
146 DuplicateKeyPolicy policy = DuplicateKeyPolicy::OVERWRITE,
147 bool throw_on_missing_key = true
148 ) : DataFrame(
149 reader,
151 .set_key_column(_key_column)
152 .set_duplicate_key_policy(policy)
153 .set_throw_on_missing_key(throw_on_missing_key)
154 ) {}
155#endif
156
161#if CSV_EXCEPTIONS_ENABLED
162 template<
163 typename KeyFunc,
164 csv::enable_if_t<csv::is_invocable_returning<KeyFunc, KeyType, const CSVRow&>::value, int> = 0
165 >
167 CSVReader& reader,
168 KeyFunc key_func,
169 DuplicateKeyPolicy policy = DuplicateKeyPolicy::OVERWRITE
170 ) {
171 CSVError error;
172 this->init_custom_from_reader(reader, key_func, policy, error);
173 internals::throw_dataframe_error(error);
174 }
175
177 template<
178 typename KeyFunc,
179 csv::enable_if_t<csv::is_invocable_returning<KeyFunc, KeyType, const CSVRow&>::value, int> = 0
180 >
182 CSVReader& reader,
183 KeyFunc key_func,
184 const DataFrameOptions& options
185 ) : DataFrame(reader, key_func, options.get_duplicate_key_policy()) {}
186
187#endif
188
190 static std::unique_ptr<DataFrame> try_create(CSVReader& reader, CSVError& error) {
191 std::unique_ptr<DataFrame> result(new DataFrame());
192 if (!result->init_unkeyed_from_reader(reader, error)) return nullptr;
193 return result;
194 }
195
196 static std::unique_ptr<DataFrame> try_create(
197 CSVReader& reader, const DataFrameOptions& options, CSVError& error
198 ) {
199 std::unique_ptr<DataFrame> result(new DataFrame());
200 if (!result->init_from_reader(reader, options, error)) return nullptr;
201 return result;
202 }
203
204 static std::unique_ptr<DataFrame> try_create(
205 csv::string_view filename, CSVError& error,
206 const CSVFormat& format = CSVFormat::guess_csv()
207 ) {
208 auto reader = CSVReader::try_open(filename, error, format);
209 return reader ? try_create(*reader, error) : nullptr;
210 }
211
212 static std::unique_ptr<DataFrame> try_create(
213 csv::string_view filename, const DataFrameOptions& options, CSVError& error,
214 const CSVFormat& format = CSVFormat::guess_csv()
215 ) {
216 auto reader = CSVReader::try_open(filename, error, format);
217 return reader ? try_create(*reader, options, error) : nullptr;
218 }
219
220 template<typename KeyFunc,
221 csv::enable_if_t<csv::is_invocable_returning<KeyFunc, KeyType, const CSVRow&>::value, int> = 0>
222 static std::unique_ptr<DataFrame> try_create(
223 CSVReader& reader, KeyFunc key_func, CSVError& error,
224 DuplicateKeyPolicy policy = DuplicateKeyPolicy::OVERWRITE
225 ) {
226 std::unique_ptr<DataFrame> result(new DataFrame());
227 if (!result->init_custom_from_reader(reader, key_func, policy, error)) return nullptr;
228 return result;
229 }
230
231 template<typename KeyFunc,
232 csv::enable_if_t<csv::is_invocable_returning<KeyFunc, KeyType, const CSVRow&>::value, int> = 0>
233 static std::unique_ptr<DataFrame> try_create(
234 CSVReader& reader, KeyFunc key_func, const DataFrameOptions& options, CSVError& error
235 ) {
236 return try_create(reader, std::move(key_func), error, options.get_duplicate_key_policy());
237 }
238
240 size_t size() const noexcept {
241 return storage_.rows().size();
242 }
243
245 bool empty() const noexcept {
246 return storage_.rows().empty();
247 }
248
250 size_t n_rows() const noexcept { return storage_.rows().size(); }
251
253 size_t n_cols() const noexcept { return storage_.n_cols(); }
254
256 bool has_column(const std::string& name) const {
257 return this->index_of(name) != CSV_NOT_FOUND;
258 }
259
261 int index_of(const std::string& name) const {
262 return this->storage_.names()->index_of(name);
263 }
264
266 const std::vector<std::string>& columns() const noexcept { return this->storage_.names()->get_col_names(); }
267
269#if CSV_EXCEPTIONS_ENABLED
270 void insert_row(size_t index, const std::vector<std::string>& row) {
271 CSVError error;
272 this->try_insert_row(index, row, error);
273 internals::throw_dataframe_error(error);
274 }
275#endif
276
280#if CSV_EXCEPTIONS_ENABLED
281 void insert_column(size_t index, const std::string& name) {
282 this->insert_column(index, name, std::string());
283 }
284#endif
285
290#if CSV_EXCEPTIONS_ENABLED
291 void insert_column(size_t index, const std::string& name, const std::string& default_value) {
292 CSVError error;
293 this->try_insert_column(index, name, default_value, error);
294 internals::throw_dataframe_error(error);
295 }
296#endif
297
299#if CSV_EXCEPTIONS_ENABLED
300 void append_column(const std::string& name) {
301 this->insert_column(this->n_cols(), name);
302 }
303#endif
304
306#if CSV_EXCEPTIONS_ENABLED
307 void append_column(const std::string& name, const std::string& default_value) {
308 this->insert_column(this->n_cols(), name, default_value);
309 }
310#endif
311
318#if CSV_EXCEPTIONS_ENABLED
319 DataFrame selected_rows(const std::vector<std::uint8_t>& include_rows) const {
320 CSVError error;
321 DataFrame result = this->try_selected_rows(include_rows, error);
322 internals::throw_dataframe_error(error);
323 return result;
324 }
325#endif
326
328 bool try_insert_row(size_t index, const std::vector<std::string>& row, CSVError& error) {
329 error.clear();
330 if (index > this->size())
331 return internals::fail_static(error, CSVErrorCode::IndexOutOfBounds, "DataFrame insert_row index out of range");
332 CSVRow inserted_row = this->storage_.make_inserted_row(row, error);
333 if (error) return false;
334 if (this->is_keyed_) {
335 if (this->key_column_index_ == CSV_NOT_FOUND)
336 return internals::fail_static(error, CSVErrorCode::UnsupportedOperation, "insert_row requires a column-keyed DataFrame");
337 CSVField field = inserted_row.try_field(static_cast<size_t>(this->key_column_index_), error);
338 if (error) return false;
339 KeyType key{};
340 CSVConversionError conversion;
341 if (!field.try_get(key, conversion)) {
342 return internals::fail_static(error, CSVErrorCode::ConversionFailed, csv_conversion_error_message(conversion));
343 }
344 this->ensure_key_index();
345 if (this->key_index_->find(key) != this->key_index_->end())
346 return internals::fail_static(error, CSVErrorCode::DuplicateKey, internals::ERROR_DUPLICATE_KEY);
347 this->storage_.reserve_insert();
348 reserve_for_append(this->keys_, 1);
349 this->keys_.insert(this->keys_.begin() + index, std::move(key));
350 } else { this->storage_.reserve_insert(); }
351 this->storage_.insert_row(index, std::move(inserted_row));
352 this->invalidate_key_index();
353 return true;
354 }
355
357 bool try_insert_column(size_t index, const std::string& name, const std::string& value, CSVError& error) {
358 if (!this->storage_.insert_column(index, name, value, this->key_column_index_, [this](CSVError& failure) {
359 return !this->is_keyed_ || this->key_storage_.preserve(
360 this->keys_, [this]() { this->invalidate_key_index(); }, failure);
361 }, error)) return false;
362 this->invalidate_column_proxies();
363 this->invalidate_json_converter();
364 return true;
365 }
366 bool try_insert_column(size_t index, const std::string& name, CSVError& error) {
367 return this->try_insert_column(index, name, std::string(), error);
368 }
369 bool try_append_column(const std::string& name, CSVError& error) {
370 return this->try_insert_column(this->n_cols(), name, error);
371 }
372 bool try_append_column(const std::string& name, const std::string& value, CSVError& error) {
373 return this->try_insert_column(this->n_cols(), name, value, error);
374 }
375
377 DataFrame try_selected_rows(const std::vector<std::uint8_t>& mask, CSVError& error) const {
378 DataFrame result;
379 result.storage_ = this->storage_.selected_rows(mask, error);
380 return result;
381 }
382
384#if CSV_EXCEPTIONS_ENABLED
386 CSVError error;
387 auto result = this->try_column_view(col_index, error);
388 internals::throw_dataframe_error(error);
389 return result;
390 }
391#endif
392
394#if CSV_EXCEPTIONS_ENABLED
395 DataFrameColumn<KeyType> column_view(size_t col_index) const {
396 CSVError error;
397 auto result = this->try_column_view(col_index, error);
398 internals::throw_dataframe_error(error);
399 return result;
400 }
401#endif
402
404#if CSV_EXCEPTIONS_ENABLED
405 DataFrameColumn<KeyType> column_view(const std::string& name) {
406 CSVError error;
407 auto result = this->try_column_view(name, error);
408 internals::throw_dataframe_error(error);
409 return result;
410 }
411#endif
412
414#if CSV_EXCEPTIONS_ENABLED
415 DataFrameColumn<KeyType> column_view(const std::string& name) const {
416 CSVError error;
417 auto result = this->try_column_view(name, error);
418 internals::throw_dataframe_error(error);
419 return result;
420 }
421#endif
422
432#if CSV_EXCEPTIONS_ENABLED
433 template<typename K = KeyType,
434 csv::enable_if_t<!std::is_integral<K>::value, int> = 0>
436 static_assert(std::is_same<K, KeyType>::value,
437 "Do not explicitly instantiate this template. Use at(size_t) for positional access.");
438 return this->at(i);
439 }
440
443 template<typename K = KeyType,
444 csv::enable_if_t<!std::is_integral<K>::value, int> = 0>
446 static_assert(std::is_same<K, KeyType>::value,
447 "Do not explicitly instantiate this template. Use at(size_t) for positional access.");
448 return this->at(i);
449 }
450
451#endif
452
458#if CSV_EXCEPTIONS_ENABLED
460 CSVError error;
461 auto result = this->try_at(i, error);
462 internals::throw_dataframe_error(error);
463 return result;
464 }
465#endif
466
468#if CSV_EXCEPTIONS_ENABLED
469 DataFrameRow<KeyType> at(size_t i) const {
470 CSVError error;
471 auto result = this->try_at(i, error);
472 internals::throw_dataframe_error(error);
473 return result;
474 }
475#endif
476
484#if CSV_EXCEPTIONS_ENABLED
485 DataFrameRow<KeyType> operator[](const KeyType& key) {
486 CSVError error;
487 auto result = this->try_find(key, error);
488 internals::throw_dataframe_error(error);
489 return result;
490 }
491#endif
492
494#if CSV_EXCEPTIONS_ENABLED
495 DataFrameRow<KeyType> operator[](const KeyType& key) const {
496 CSVError error;
497 auto result = this->try_find(key, error);
498 internals::throw_dataframe_error(error);
499 return result;
500 }
501#endif
502
508#if CSV_EXCEPTIONS_ENABLED
509 bool contains(const KeyType& key) const {
510 CSVError error;
511 const bool result = this->try_contains(key, error);
512 internals::throw_dataframe_error(error);
513 return result;
514 }
515#endif
516
524#if CSV_EXCEPTIONS_ENABLED
525 template<typename T = std::string>
526 std::vector<T> column(const std::string& name) const {
527 CSVError error;
528 auto result = this->template try_column<T>(name, error);
529 internals::throw_dataframe_error(error);
530 return result;
531 }
532#endif
533
535 row_type try_at(size_t index, CSVError& error) {
536 if (!this->validate_row_index(index, error)) return {};
537 return this->make_row_proxy(index);
538 }
539 row_type try_at(size_t index, CSVError& error) const {
540 if (!this->validate_row_index(index, error)) return {};
541 return this->make_const_row_proxy(index);
542 }
543 row_type try_find(const KeyType& key, CSVError& error) {
544 const size_t index = this->try_position_of(key, error);
545 return error ? row_type() : this->make_row_proxy(index);
546 }
547 row_type try_find(const KeyType& key, CSVError& error) const {
548 const size_t index = this->try_position_of(key, error);
549 return error ? row_type() : this->make_const_row_proxy(index);
550 }
551 bool try_contains(const KeyType& key, CSVError& error) const {
552 error.clear();
553 if (!this->require_keyed_frame(error)) return false;
554 this->ensure_key_index();
555 return this->key_index_->find(key) != this->key_index_->end();
556 }
557 column_type try_column_view(size_t index, CSVError& error) {
558 if (!this->validate_column_index(index, error)) return {};
559 return column_type(this, index);
560 }
561 column_type try_column_view(size_t index, CSVError& error) const {
562 if (!this->validate_column_index(index, error)) return {};
563 return column_type(this, index);
564 }
565 column_type try_column_view(const std::string& name, CSVError& error) {
566 const size_t index = this->try_find_column(name, error);
567 return error ? column_type() : column_type(this, index);
568 }
569 column_type try_column_view(const std::string& name, CSVError& error) const {
570 const size_t index = this->try_find_column(name, error);
571 return error ? column_type() : column_type(this, index);
572 }
573 template<typename T = std::string>
574 std::vector<T> try_column(const std::string& name, CSVError& error) const {
575 const auto view = this->try_column_view(name, error);
576 return error ? std::vector<T>() : view.template try_to_vector<T>(error);
577 }
578
592#if CSV_EXCEPTIONS_ENABLED
593 template<typename State, typename Fn>
595 DataFrameExecutor& executor,
596 std::vector<State>& states,
597 Fn&& fn
598 ) const {
599 CSVError error;
600 this->try_column_parallel_apply(executor, states, std::forward<Fn>(fn), error);
601 internals::throw_dataframe_error(error);
602 }
603#endif
604
605 template<typename State, typename Fn>
606 bool try_column_parallel_apply(
607 DataFrameExecutor& executor,
608 std::vector<State>& states,
609 Fn&& fn,
610 CSVError& error
611 ) const {
612 error.clear();
613 if (states.size() != this->n_cols()) {
614 return internals::fail_static(error, CSVErrorCode::InvalidArgument, internals::ERROR_COLUMN_APPLY_STATE_COUNT);
615 }
616
617 executor.parallel_for(this->n_cols(), [this, &states, &fn](size_t column_index) {
618 fn(column_type(this, column_index), states[column_index]);
619 });
620 return true;
621 }
622
631#if CSV_EXCEPTIONS_ENABLED
632 template<typename State, typename Fn>
634 DataFrameExecutor& executor,
635 const std::vector<size_t>& column_indices,
636 std::vector<State>& states,
637 Fn&& fn
638 ) const {
639 CSVError error;
640 this->try_column_parallel_apply(executor, column_indices, states, std::forward<Fn>(fn), error);
641 internals::throw_dataframe_error(error);
642 }
643#endif
644
645 template<typename State, typename Fn>
646 bool try_column_parallel_apply(
647 DataFrameExecutor& executor,
648 const std::vector<size_t>& column_indices,
649 std::vector<State>& states,
650 Fn&& fn,
651 CSVError& error
652 ) const {
653 error.clear();
654 if (states.size() != column_indices.size()) {
655 return internals::fail_static(error, CSVErrorCode::InvalidArgument, internals::ERROR_COLUMN_APPLY_SUBSET_STATE_COUNT);
656 }
657
658 if (!this->validate_selected_columns(column_indices, error)) return false;
659
660 executor.parallel_for(column_indices.size(), [this, &column_indices, &states, &fn](size_t selected_index) {
661 const size_t column_index = column_indices[selected_index];
662 fn(column_type(this, column_index), states[selected_index]);
663 });
664 return true;
665 }
666
673#if CSV_EXCEPTIONS_ENABLED
674 template<typename Fn>
676 DataFrameExecutor& executor,
677 Fn&& fn
678 ) const {
679 CSVError error;
680 this->try_column_parallel_apply(executor, std::forward<Fn>(fn), error);
681 internals::throw_dataframe_error(error);
682 }
683#endif
684
685 template<typename Fn>
686 bool try_column_parallel_apply(
687 DataFrameExecutor& executor,
688 Fn&& fn,
689 CSVError& error
690 ) const {
691 error.clear();
692 executor.parallel_for(this->n_cols(), [this, &fn](size_t column_index) {
693 fn(column_type(this, column_index));
694 });
695 return true;
696 }
697
705#if CSV_EXCEPTIONS_ENABLED
706 template<typename Fn>
708 DataFrameExecutor& executor,
709 const std::vector<size_t>& column_indices,
710 Fn&& fn
711 ) const {
712 CSVError error;
713 this->try_column_parallel_apply(executor, column_indices, std::forward<Fn>(fn), error);
714 internals::throw_dataframe_error(error);
715 }
716#endif
717
718 template<typename Fn>
719 bool try_column_parallel_apply(
720 DataFrameExecutor& executor,
721 const std::vector<size_t>& column_indices,
722 Fn&& fn,
723 CSVError& error
724 ) const {
725 error.clear();
726 if (!this->validate_selected_columns(column_indices, error)) return false;
727
728 executor.parallel_for(column_indices.size(), [this, &column_indices, &fn](size_t selected_index) {
729 fn(column_type(this, column_indices[selected_index]));
730 });
731 return true;
732 }
733
739 template<
740 typename GroupFunc,
741 typename GroupKey = invoke_result_t<GroupFunc, DataFrameRow<KeyType>>,
742 csv::enable_if_t<
743 internals::is_hashable<GroupKey>::value &&
744 internals::is_equality_comparable<GroupKey>::value,
745 int
746 > = 0
747 >
748 std::unordered_map<GroupKey, std::vector<size_t>> group_by(GroupFunc group_func) const {
749 std::unordered_map<GroupKey, std::vector<size_t>> grouped;
750
751 for (size_t i = 0; i < storage_.rows().size(); i++) {
752 GroupKey group_key = group_func(this->make_const_row_proxy(i));
753 grouped[group_key].push_back(i);
754 }
755
756 return grouped;
757 }
758
764#if CSV_EXCEPTIONS_ENABLED
765 std::unordered_map<std::string, std::vector<size_t>> group_by(const std::string& name) const {
766 CSVError error;
767 auto result = this->try_group_by(name, error);
768 internals::throw_dataframe_error(error);
769 return result;
770 }
771#endif
772
774 std::unordered_map<std::string, std::vector<size_t>> try_group_by(const std::string& name, CSVError& error) const {
775 std::unordered_map<std::string, std::vector<size_t>> grouped;
776 const size_t column = this->try_find_column(name, error);
777 if (error) return grouped;
778 for (size_t i = 0; i < this->size(); ++i) {
779 const auto cell = this->try_make_cell(i, column, false, error);
780 if (error) return {};
781 grouped[std::string(cell.get_sv())].push_back(i);
782 }
783 return grouped;
784 }
785
787 iterator begin() { return iterator(this, 0); }
788
790 iterator end() { return iterator(this, this->size()); }
791
793 const_iterator begin() const { return const_iterator(this, 0); }
794
796 const_iterator end() const { return const_iterator(this, this->size()); }
797
799 const_iterator cbegin() const { return const_iterator(this, 0); }
800
802 const_iterator cend() const { return const_iterator(this, this->size()); }
803
804 private:
806 bool is_keyed_ = false;
807
809
811 std::vector<KeyType> keys_;
813
815 int key_column_index_ = CSV_NOT_FOUND;
816
818 mutable std::unique_ptr<std::unordered_map<KeyType, size_t>> key_index_;
820 std::shared_ptr<size_t> column_generation_{ new size_t(0) };
821
823 bool init_unkeyed_from_reader(CSVReader& reader, CSVError& error) {
824 error.clear();
825 this->assert_fresh_storage(false);
826 this->storage_.set_names(reader.col_names_ptr());
827 std::vector<CSVRow> batch;
828 while (reader.try_read_chunk(batch, dataframe_read_chunk_rows(), error)) {
829 this->append_unkeyed_batch(batch);
830 }
831 return !error;
832 }
833
835 void init_unkeyed_from_rows(std::vector<CSVRow>& source_rows) {
836 this->assert_fresh_storage(false);
837 this->is_keyed_ = false;
838 this->storage_.set_names(source_rows.empty()
839 ? internals::ConstColNamesPtr(std::make_shared<internals::ColNames>())
840 : source_rows.front().col_names_ptr());
841 this->storage_.rows() = std::move(source_rows);
842 }
843
845 bool init_from_reader(CSVReader& reader, const DataFrameOptions& options, CSVError& error) {
846 error.clear();
847 this->assert_fresh_storage(false);
848 this->is_keyed_ = true;
849 this->storage_.set_names(reader.col_names_ptr());
850 const std::string& name = options.get_key_column();
851 if (name.empty())
852 return internals::fail_static(error, CSVErrorCode::MissingKeyColumn, internals::ERROR_KEY_COLUMN_EMPTY);
853 const int index = this->storage_.names()->index_of(name);
854 if (index == CSV_NOT_FOUND)
855 return internals::fail(error, CSVErrorCode::MissingKeyColumn, std::string(internals::ERROR_KEY_COLUMN_NOT_FOUND) + name);
856 this->key_column_index_ = index;
857 auto extract = [&options, &name](const CSVRow& row, KeyType& key, CSVError& failure) {
858 CSVField field = row.try_field(name, failure);
860 if (!failure && field.try_get(key, conversion)) return true;
861 if (!options.get_throw_on_missing_key()) {
862 failure.clear();
863 key = KeyType();
864 return true;
865 }
866 if (!failure) {
867 internals::fail_static(failure, CSVErrorCode::ConversionFailed, csv_conversion_error_message(conversion));
868 }
869 failure.set_context(std::string(internals::ERROR_KEY_COLUMN_VALUE) + std::string(failure.context));
870 return false;
871 };
872 return this->build_from_key_function(reader, extract, options.get_duplicate_key_policy(), error);
873 }
874
875 template<typename KeyFunc>
876 bool init_custom_from_reader(CSVReader& reader, KeyFunc& key_func, DuplicateKeyPolicy policy, CSVError& error) {
877 this->storage_.set_names(reader.col_names_ptr());
878 this->is_keyed_ = true;
879 auto extract = [&key_func](const CSVRow& row, KeyType& key, CSVError&) {
880 key = key_func(row);
881 return true;
882 };
883 return this->build_from_key_function(reader, extract, policy, error);
884 }
885
887 template<typename KeyFunc>
888 bool build_from_key_function(
889 CSVReader& reader, KeyFunc& key_func, DuplicateKeyPolicy policy, CSVError& error
890 ) {
891 error.clear();
892 std::unordered_map<KeyType, size_t> key_to_pos;
893 this->assert_fresh_storage(true);
894 std::vector<CSVRow> batch;
895 while (reader.try_read_chunk(batch, dataframe_read_chunk_rows(), error)) {
896 if (!this->append_keyed_batch(batch, key_func, policy, key_to_pos, error)) return false;
897 }
898 return !error;
899 }
900
901 static size_t dataframe_read_chunk_rows() noexcept {
902 return 50000;
903 }
904
905 template<typename Container>
906 static void reserve_for_append(Container& container, size_t additional) {
907 if (additional == 0) {
908 return;
909 }
910
911 const size_t required = container.size() + additional;
912 if (required <= container.capacity()) {
913 return;
914 }
915
916 const size_t current = container.capacity();
917 // Do not reserve exactly one batch ahead. DataFrame construction
918 // appends fixed-size read_chunk() batches, so exact reserves would
919 // force a reallocation on every batch for large inputs.
920 size_t next = current == 0 ? additional : current * 2;
921 if (next < required || next < current) {
922 next = required;
923 }
924
925 container.reserve(next);
926 }
927
928 void append_unkeyed_batch(std::vector<CSVRow>& batch) {
929 reserve_for_append(storage_.rows(), batch.size());
930
931 for (auto& row : batch) {
932 storage_.rows().push_back(std::move(row));
933 }
934 }
935
936 template<typename KeyFunc>
937 bool append_keyed_batch(
938 std::vector<CSVRow>& batch, KeyFunc& key_func, DuplicateKeyPolicy policy,
939 std::unordered_map<KeyType, size_t>& key_to_pos, CSVError& error
940 ) {
941 reserve_for_append(storage_.rows(), batch.size());
942 reserve_for_append(keys_, batch.size());
943 for (auto& row : batch) {
944 KeyType key{};
945 if (!key_func(row, key, error)) return false;
946 auto existing = key_to_pos.find(key);
947 if (existing != key_to_pos.end()) {
948 if (policy == DuplicateKeyPolicy::THROW)
949 return internals::fail_static(error, CSVErrorCode::DuplicateKey, internals::ERROR_DUPLICATE_KEY);
950 if (policy == DuplicateKeyPolicy::OVERWRITE)
951 storage_.rows()[existing->second] = std::move(row);
952 continue;
953 }
954 storage_.rows().push_back(std::move(row));
955 keys_.push_back(key);
956 key_to_pos[key] = storage_.rows().size() - 1;
957 }
958 return true;
959 }
960
962 size_t try_find_column(const std::string& name, CSVError& error) const {
963 error.clear();
964 const int index = this->index_of(name);
965 if (index == CSV_NOT_FOUND) {
966 internals::fail(error, CSVErrorCode::ColumnNotFound, std::string(internals::ERROR_COLUMN_NOT_FOUND) + name);
967 return 0;
968 }
969 return static_cast<size_t>(index);
970 }
971
972 bool validate_row_index(size_t index, CSVError& error) const {
973 error.clear();
974 return index < this->size() || internals::fail_static(error, CSVErrorCode::IndexOutOfBounds, "DataFrame row index out of range");
975 }
976 bool validate_column_index(size_t index, CSVError& error) const {
977 error.clear();
978 return index < this->n_cols() || internals::fail_static(error, CSVErrorCode::IndexOutOfBounds, internals::ERROR_COLUMN_INDEX_OUT_OF_RANGE);
979 }
980 DataFrameCell try_make_cell(size_t row_index, size_t column_index, bool mutable_access, CSVError& error) const {
981 const auto binding = this->storage_.try_bind(row_index, column_index, error);
982 if (error) return {};
983 const auto* row = &this->storage_.rows()[row_index];
984 return mutable_access
985 ? DataFrameCell(row, binding.overlay, binding.physical_column, &this->storage_, true)
986 : DataFrameCell(row, binding.overlay, binding.physical_column);
987 }
988
989 DataFrameCell make_cell(size_t row_index, size_t column_index, bool mutable_access) const {
990 return this->make_cell(&this->storage_.rows()[row_index], row_index, column_index, mutable_access);
991 }
992
993 DataFrameCell make_cell(const CSVRow* row, size_t row_index, size_t column_index, bool mutable_access) const {
994 const auto binding = this->storage_.bind(row_index, column_index);
995 return mutable_access
996 ? DataFrameCell(row, binding.overlay, binding.physical_column, &this->storage_, true)
997 : DataFrameCell(row, binding.overlay, binding.physical_column);
998 }
999
1000 csv::string_view cell_view(size_t row_index, size_t column_index) const {
1001 return this->storage_.view(row_index, column_index);
1002 }
1003
1004 DataFrameRow<KeyType> make_row_proxy(size_t row_index) {
1005 const auto& row = storage_.rows()[row_index];
1006 return DataFrameRow<KeyType>(&row, this, row_index, static_cast<RowOverlay*>(nullptr), this->key_ptr_at(row_index));
1007 }
1008
1009 DataFrameRow<KeyType> make_const_row_proxy(size_t row_index) const {
1010 const auto& row = storage_.rows()[row_index];
1011 return DataFrameRow<KeyType>(&row, this, row_index, static_cast<const RowOverlay*>(nullptr), this->key_ptr_at(row_index));
1012 }
1013
1014 bool erase_column_at_index(size_t column_index, CSVError& error) {
1015 if (!this->storage_.erase_column(column_index, this->key_column_index_, error)) return false;
1016 this->invalidate_column_proxies();
1017 this->invalidate_json_converter();
1018 return true;
1019 }
1020
1021 bool erase_at_index(size_t row_index) {
1022 if (row_index >= storage_.rows().size()) {
1023 return false;
1024 }
1025
1026 this->storage_.erase_row(row_index);
1027 if (this->is_keyed_) {
1028 keys_.erase(keys_.begin() + row_index);
1029 }
1030 this->invalidate_key_index();
1031 return true;
1032 }
1033
1034 bool validate_selected_columns(const std::vector<size_t>& column_indices, CSVError& error) const {
1035 error.clear();
1036 for (size_t index : column_indices) {
1037 if (index >= this->n_cols())
1038 return internals::fail_static(error, CSVErrorCode::IndexOutOfBounds, internals::ERROR_COLUMN_APPLY_INVALID_INDEX);
1039 }
1040 return true;
1041 }
1042
1044 bool require_keyed_frame(CSVError& error) const {
1045 if (!this->is_keyed_)
1046 return internals::fail_static(error, CSVErrorCode::UnkeyedFrame, internals::ERROR_UNKEYED_DATA_FRAME);
1047 return true;
1048 }
1049
1051 void invalidate_key_index() {
1052 key_index_.reset();
1053 }
1054
1056 void invalidate_json_converter() {
1057 json_converter_ = internals::lazy_shared_ptr<internals::JsonConverter>();
1058 }
1059
1061 void invalidate_column_proxies() {
1062 ++(*column_generation_);
1063 }
1064
1066 void assert_fresh_storage(bool expected_is_keyed) const {
1067 CSV_DEBUG_ASSERT(this->storage_.rows().empty());
1068 CSV_DEBUG_ASSERT(this->keys_.empty());
1069 CSV_DEBUG_ASSERT(this->storage_.pristine());
1070 CSV_DEBUG_ASSERT(this->key_index_.get() == nullptr);
1071 CSV_DEBUG_ASSERT(this->json_converter_.get() == nullptr);
1072 CSV_DEBUG_ASSERT(this->is_keyed_ == expected_is_keyed);
1073 }
1074
1076 void ensure_key_index() const {
1077 if (key_index_) return;
1078
1079 key_index_ = std::unique_ptr<std::unordered_map<KeyType, size_t>>(
1080 new std::unordered_map<KeyType, size_t>()
1081 );
1082
1083 for (size_t i = 0; i < storage_.rows().size(); i++) {
1084 (*key_index_)[keys_[i]] = i;
1085 }
1086 }
1087
1089 size_t try_position_of(const KeyType& key, CSVError& error) const {
1090 error.clear();
1091 if (!this->require_keyed_frame(error)) return 0;
1092 this->ensure_key_index();
1093 const auto found = this->key_index_->find(key);
1094 if (found == this->key_index_->end()) {
1095 internals::fail_static(error, CSVErrorCode::KeyNotFound, internals::ERROR_KEY_NOT_FOUND);
1096 return 0;
1097 }
1098 return found->second;
1099 }
1100
1101 const KeyType* key_ptr_at(size_t row_index) const {
1102 return this->is_keyed_ ? &keys_[row_index] : nullptr;
1103 }
1104 };
1105
1106#if defined(CSV_HAS_CXX20) && CSV_EXCEPTIONS_ENABLED
1107 static_assert(
1108 internals::csv_write_rows_input_range<DataFrame<>>,
1109 "DataFrame must remain compatible with csv::DelimWriter::write_rows()."
1110 );
1111#endif
1112}
Data type representing individual CSV values.
Definition csv_row.hpp:74
bool try_get(T &out) const noexcept
Non-throwing equivalent of get().
Definition csv_row.hpp:188
Stores information about how to parse a CSV file.
static CSVFormat guess_csv()
CSVFormat preset for delimiter inference with header/n_cols inference enabled.
Main class for parsing CSVs from files and in-memory sources.
static std::unique_ptr< CSVReader > try_open(csv::string_view filename, CSVError &error, const CSVFormat &format=CSVFormat::guess_csv())
Construct CSVReader from filename.
Data structure for representing CSV rows.
Definition csv_row.hpp:533
CSVField try_field(size_t index, CSVError &error) const
Checked field retrieval.
Definition csv_row.cpp:47
DuplicateKeyPolicy
Policy for handling duplicate keys when creating a keyed DataFrame.
bool try_insert_column(size_t index, const std::string &name, const std::string &value, CSVError &error)
Materialize the visible schema after validating the requested insertion.
bool empty() const noexcept
Check if the DataFrame is empty (has no rows).
static std::unique_ptr< DataFrame > try_create(CSVReader &reader, CSVError &error)
Read a frame, returning no object on validation or input failure.
bool try_insert_row(size_t index, const std::vector< std::string > &row, CSVError &error)
Insert a row; validation failures leave the frame unchanged.
DataFrame(CSVReader &reader, const std::string &_key_column, DuplicateKeyPolicy policy=DuplicateKeyPolicy::OVERWRITE, bool throw_on_missing_key=true)
Construct a keyed DataFrame using a column name as the key.
void insert_column(size_t index, const std::string &name, const std::string &default_value)
Insert a new column with a default value.
DataFrameRow< KeyType > operator[](size_t i)
Access a row by position with bounds checking.
DataFrameColumn< KeyType > column_view(const std::string &name)
Access a column view by name.
DataFrame(CSVReader &reader, const DataFrameOptions &options)
Construct a keyed DataFrame from a CSV reader with options.
bool has_column(const std::string &name) const
Check if a column exists in the DataFrame.
iterator end()
Get iterator past the last row.
DataFrameRow< KeyType > operator[](const KeyType &key)
Access a row by its key.
void insert_column(size_t index, const std::string &name)
Insert a new column with empty values.
const_iterator cend() const
Get const iterator past the last row (explicit).
std::vector< T > column(const std::string &name) const
Extract all values from a column with type conversion.
size_t n_cols() const noexcept
Get the number of visible columns in the DataFrame.
std::unordered_map< GroupKey, std::vector< size_t > > group_by(GroupFunc group_func) const
Group row positions using an arbitrary grouping function.
iterator begin()
Get iterator to the first row.
DataFrame(CSVReader &reader, KeyFunc key_func, DuplicateKeyPolicy policy=DuplicateKeyPolicy::OVERWRITE)
Construct a keyed DataFrame using a custom key function.
void append_column(const std::string &name, const std::string &default_value)
Append a new column with a default value.
DataFrame try_selected_rows(const std::vector< std::uint8_t > &mask, CSVError &error) const
Selection preserves visible edits and returns an empty frame on failure.
DataFrame(csv::string_view filename, const DataFrameOptions &options, CSVFormat format=CSVFormat::guess_csv())
Construct a keyed DataFrame directly from a CSV file.
void column_parallel_apply(DataFrameExecutor &executor, const std::vector< size_t > &column_indices, std::vector< State > &states, Fn &&fn) const
Apply a batch-oriented function to a selected subset of columns, potentially in parallel.
void insert_row(size_t index, const std::vector< std::string > &row)
Insert a new row at the specified index.
DataFrame(CSVReader &reader, KeyFunc key_func, const DataFrameOptions &options)
Construct a keyed DataFrame using a custom key function with options.
DataFrame selected_rows(const std::vector< std::uint8_t > &include_rows) const
Build an unkeyed DataFrame containing rows whose corresponding mask entry is true.
size_t n_rows() const noexcept
Get the number of rows in the DataFrame.
const std::vector< std::string > & columns() const noexcept
Get the column names in order.
const_iterator cbegin() const
Get const iterator to the first row (explicit).
void column_parallel_apply(DataFrameExecutor &executor, std::vector< State > &states, Fn &&fn) const
Apply a batch-oriented function to each column, potentially in parallel.
DataFrameRow< KeyType > operator[](size_t i) const
Access a row by position with bounds checking (const version).
void column_parallel_apply(DataFrameExecutor &executor, const std::vector< size_t > &column_indices, Fn &&fn) const
Apply a read-only batch function to a selected subset of columns, potentially in parallel.
const_iterator begin() const
Get const iterator to the first row.
DataFrameRow< KeyType > at(size_t i) const
Access a row by position with bounds checking (const version).
DataFrameRow< KeyType > operator[](const KeyType &key) const
Access a row by its key (const version).
DataFrameColumn< KeyType > column_view(const std::string &name) const
Access a column view by name.
DataFrameColumn< KeyType > column_view(size_t col_index)
Access a column view by position.
bool contains(const KeyType &key) const
Check if a key exists in the DataFrame.
const_iterator end() const
Get const iterator past the last row.
std::unordered_map< std::string, std::vector< size_t > > try_group_by(const std::string &name, CSVError &error) const
Group visible string values, reporting lookup or short-row errors.
void append_column(const std::string &name)
Append a new column with empty values.
int index_of(const std::string &name) const
Get the index of a column by name.
DataFrameRow< KeyType > at(size_t i)
Access a row by position with bounds checking.
row_type try_at(size_t index, CSVError &error)
Checked positional row access.
DataFrame(csv::string_view filename, CSVFormat format=CSVFormat::guess_csv())
Construct an unkeyed DataFrame directly from a CSV file.
void column_parallel_apply(DataFrameExecutor &executor, Fn &&fn) const
Apply a read-only batch function to each column, potentially in parallel.
DataFrame(std::vector< CSVRow > rows)
Construct an unkeyed DataFrame from an existing batch of rows.
size_t size() const noexcept
Get the number of rows in the DataFrame.
std::unordered_map< std::string, std::vector< size_t > > group_by(const std::string &name) const
Group row positions by the value of a column.
DataFrameColumn< KeyType > column_view(size_t col_index) const
Access a column view by position.
DataFrame()=default
Construct an empty DataFrame.
DataFrame(CSVReader &reader)
Construct an unkeyed DataFrame from a CSV reader.
Borrow a row and error state for synchronous, single-pass field access.
C++11 tagged choice of concrete backends, without a vtable.
The all encompassing namespace.
CSVConversionError
Non-throwing CSVField conversion result.
Definition csv_error.hpp:23
@ None
Conversion succeeded.
const char * csv_conversion_error_message(CSVConversionError error) noexcept
Return a stable human-readable description for a CSVConversionError.
Definition csv_error.hpp:53
@ IndexOutOfBounds
A row/column position exceeds the visible schema or physical record width.
@ InvalidArgument
Invalid operation input, such as a null stream or zero batch size.
@ KeyNotFound
A requested row key is absent from the frame's key index.
@ MissingKeyColumn
Keyed construction has an empty key-column name or a name absent from the schema.
@ DuplicateKey
A key conflicts with an existing row under the requested insertion/duplicate policy.
@ ColumnNotFound
A requested name is absent from the visible column schema.
@ UnkeyedFrame
A key-based operation was requested on a positional-only frame.
@ UnsupportedOperation
The operation cannot be performed for this frame, such as inserting into a custom-key frame.
@ ConversionFailed
A DataFrame field/key conversion failed; context describes the cause.
constexpr int CSV_NOT_FOUND
Integer indicating a requested column wasn't found.
Definition common.hpp:494
std::string_view string_view
The string_view class used by this library.
Definition common.hpp:182
A diagnostic whose text remains valid for the lifetime of the error.
Definition csv_error.hpp:94