A lightweight DataFrame library in C, built during a 1-week EPITECH project by a 3-person team.
cuddle provides a pandas-like workflow for C projects:
- read CSV files into a typed table structure,
- inspect and display data,
- retrieve values,
- apply common data operations (sort/filter/apply/groupby),
- write data back to CSV.
- Project Goals
- Repository Structure
- Core Data Model
- Implemented Features
- Build and Run
- Typical Usage Flow
- Memory Management Rules
- Current Limitations
This project focuses on building a reusable C static library (libcuddle.a) for tabular data manipulation.
Main design ideas:
- typed columns (
BOOL,INT,UINT,FLOAT,STRING), - generic storage (
void ***data) with explicit type metadata, - function-pointer based operations for flexible sorting/filtering/transforms,
- simple CSV integration for import/export.
.
├── Makefile
├── include/
│ ├── dataframe.h
│ └── utils/
│ └── utils.h
└── src/
└── dataframe/
├── df_free.c
├── df_init_struct.c
├── df_display/
│ ├── df_describe.c
│ ├── df_display.c
│ ├── df_head.c
│ ├── df_info.c
│ ├── df_shape.c
│ └── df_tail.c
├── df_get_value/
│ ├── df_copy_data.c
│ ├── df_get_index.c
│ ├── df_get_unique_values.c
│ ├── df_get_value.c
│ └── df_get_values.c
├── df_operate/
│ ├── df_apply.c
│ ├── df_filter.c
│ ├── df_groupby.c
│ ├── df_sort_copy_utils.c
│ ├── df_sort_copy.c
│ ├── df_sort_quicksort.c
│ ├── df_sort.c
│ └── df_to_type.c
├── df_read_csv/
│ ├── df_convert_data.c
│ ├── df_detect_types.c
│ └── df_read_csv.c
└── df_write_csv/
└── df_write_csv.c
Declared in include/dataframe.h:
-
dataframe_tnb_rows,nb_columns: table dimensionscolumn_names: array of column labelscolumn_types: array of type tags (column_type_t)data: matrix of values (data[row][column])separator: CSV separator used for parsing/writing
-
column_type_tBOOL,INT,UINT,FLOAT,STRING,UNDEFINED
-
dataframe_shape_t- lightweight struct used by
df_shape()
- lightweight struct used by
Utility and operation context structs are in include/utils/utils.h (sort, apply, cast, groupby, describe helpers).
df_init_structure(separator): initialize an empty dataframe structuredf_free(df): free all allocated memory recursively
df_read_csv(filename, separator)- parses headers and rows,
- analyzes types per column,
- converts data from string to inferred typed values.
df_write_csv(dataframe, filename)- writes typed values back to CSV using dataframe separator.
df_display(df): print table contentdf_info(df): print column metadata and shapedf_shape(df): return{nb_rows, nb_columns}df_head(df, n): deep-copy firstnrowsdf_tail(df, n): deep-copy lastnrowsdf_describe(df): basic numeric statistics (count, mean, std, min, max)
df_get_value(df, row, column): pointer to one internal celldf_get_values(df, column): deep-copied, NULL-terminated array of a columndf_get_unique_values(df, column): deep-copied unique values, NULL-terminated
df_sort(df, column, sort_func): returns sorted copy of dataframedf_filter(df, column, filter_func): returns filtered copy of dataframedf_apply(df, column, apply_func): returns copy with transformed column valuesdf_groupby(df, aggregate_by, to_aggregate, agg_func): grouped + aggregated result
The project is built as a static library:
makeThis produces:
libcuddle.a
Useful Make targets:
make clean # remove objects and test artifacts
make fclean # clean + remove libcuddle.a
make re # full rebuild
make tests_run # build and run criterion tests (if tests/ exists)
make test # compile and run tests/main.c
make try # run with sanitizers enabledNotes:
- Compiler in this project is configured as
epiclangin the Makefile. - Link with
-Iincludeandlibcuddle.ain your own test/program.
#include "dataframe.h"
int main(void)
{
dataframe_t *df = df_read_csv("data.csv", ",");
if (!df)
return 84;
df_info(df);
df_display(df);
df_describe(df);
dataframe_t *first_rows = df_head(df, 5);
dataframe_t *filtered = df_filter(df, "age", my_predicate);
dataframe_t *sorted = df_sort(df, "name", my_sort_cmp);
df_write_csv(df, "output.csv");
df_free(sorted);
df_free(filtered);
df_free(first_rows);
df_free(df);
return 0;
}Because this is C and the project does explicit allocations, ownership is important:
- Always call
df_free()on every dataframe returned by operations (df_head,df_tail,df_sort,df_filter,df_apply,df_groupby, etc.). df_get_value()returns a pointer to internal storage (do not free it, do not keep it after dataframe destruction).- Arrays returned by
df_get_values()/df_get_unique_values()are deep-copied by design and should be freed by the caller according to how they are used.
df_to_type()is present in the API but currently not implemented (stub behavior).- Type inference is heuristic-based and depends on CSV content consistency.
- Some API declarations in headers are duplicated and can be cleaned in a future refactor.
- The project assumes EPITECH toolchain conventions (
epiclang, Makefile workflow).
This repository was developed in an intensive 1-week sprint by a 3-student team. The code is organized by feature area to keep implementation readable and reviewable in a short production window.