Library / SDK
serengil/chefboost avatar
serengil/chefboost

ChefBoost: Decision Trees That Emit Python Instead of Pickles

A Lightweight Decision Tree Framework supporting regular algorithms: ID3, C4.5, CART, CHAID and Regression Trees; some advanced techniques: Gradient Boosting, Random Forest and Adaboost w/categorical features support for Python

487 stars101 forksPythonMIT

At a glance

What is it?
ChefBoost is an MIT-licensed Python library that builds ID3, C4.5, CART, CHAID and regression trees plus gradient boosting, random forest and adaboost variants, and writes each trained tree out as a runnable Python if-statement file. The core judgement: its categorical handling and readable rule output are the reason to pick it, and the same design is the reason not to use it on wide numeric data.
Who is it for?
Adopt ChefBoost if your dataset is small to medium, mixes nominal and numeric columns, and you need to hand a human a readable rule file or ship a tree without a model runtime. Do not adopt it if you need GPU training, sparse high-dimensional input, calibrated probabilities, or a scikit-learn-compatible estimator for pipelines and cross-validation tooling.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 125 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

The gap ChefBoost fills: nominal columns without an encoding step

Most Python tree implementations assume a numeric matrix. Feed them a column of strings and you are expected to one-hot encode, label encode, or otherwise convert the data before the algorithm ever sees it. ChefBoost takes the opposite position. The README states that it handles both numeric and nominal features and target values, and that you do not have to apply any pre-processing to build trees. That single sentence is the product thesis.

The audience follows from it. Someone working through a textbook example like the golf dataset, where Outlook, Temperature, Humidity and Wind are all categorical, can pass a pandas DataFrame straight into chef.fit and get a tree. The same applies to survey data, configuration data, or any table where the columns are labels rather than measurements. If your pipeline already ends in a NumPy array, the library offers you nothing you cannot get elsewhere. If it ends in a DataFrame full of strings, it removes a step that is easy to get wrong, particularly the part where one-hot encoding silently changes how a split is scored.

The cost of that convenience is worth naming now: algorithms like ID3, C4.5 and CHAID are defined in terms of categorical splits, and the library implements them faithfully. That fidelity is the feature. It is also why the library is not a drop-in replacement for a general-purpose gradient boosting package.

How the fitting loop works, and why the output is a .py file

The README describes the regular tree path plainly: the algorithm finds the best feature and the best split point maximizing information gain, then builds trees recursively in child nodes. Which metric drives that search depends on the algorithm you select. The table in the README maps ID3 to entropy and information gain, C4.5 to entropy and gain ratio, CART to GINI, CHAID to chi square, and the regression tree to standard deviation. That table is the most useful piece of documentation in the repository, because it tells you the selection criterion is not configurable independently of the algorithm name. Choosing 'C4.5' chooses gain ratio.

The unusual part is persistence. Built trees are stored as Python if statements in the tests/outputs/rules directory, and the README shows a findDecision function with nested if/elif branches over Outlook, Wind and Humidity. This is not a serialization format bolted on afterward. The tree is code. You can read it, diff it in a pull request, and run it in an environment that has no ChefBoost installed at all. For anyone who has tried to explain a model to a domain expert, that property is the whole appeal.

The boosting and bagging paths change the shape of the result. Gradient boosting is described as building a tree and then building another based on the previous one's error, with predictions being the sum of each tree's prediction result. Random forest splits the dataset into several sub datasets, builds a tree per subset, and averages the predictions. Both are configured through flags rather than an algorithm name, which means the config dictionary is doing double duty: {'algorithm': 'C4.5'} for a single tree, {'enableGBM': True, ...} or {'enableRandomForest': True, ...} for an ensemble.

Install and the first fit: the commands the README actually gives

Installation is a single PyPI command. The README states it will install the library and its prerequisites as well:

pip install chefboost

Import is aliased:

from chefboost import Chefboost as chef

A minimal fit takes a DataFrame, a config dictionary, and the name of the target column:

import pandas as pd

df = pd.read_csv("dataset/golf.txt") config = {'algorithm': 'C4.5'} model = chef.fit(df, config = config, target_label = 'Decision')

Prediction on a new instance is positional, and the order must match the feature order in the training frame:

prediction = chef.predict(model, param = ['Sunny', 'Hot', 'High', 'Weak'])

The sample configurations are where the library's scope becomes concrete. For regular trees, set algorithm to 'ID3', 'C4.5', 'CART', 'CHAID' or 'Regression'. For gradient boosting, the README gives {'enableGBM': True, 'epochs': 7, 'learning_rate': 1, 'max_depth': 5}. For random forest, {'enableRandomForest': True, 'num_of_trees': 5}. Adaboost is listed as supported with a tutorial link, but the README excerpt available here does not show its config keys, so treat the exact adaboost parameter names as unverified until you read the full README or the source.

The README also points at tests/global-unit-test.py as the guide for building different trees and making predictions. That file is the practical starting point if the top-level examples do not cover your case, and it is worth reading before you file anything as a bug.

Restoring a tree, and the two-file dependency nobody mentions in the quickstart

There are two distinct reload paths in the README and they are not interchangeable.

The first loads the generated rule module directly. You point restoreTree at the module path without the .py extension:

module_name = "outputs/rules/rules" tree = chef.restoreTree(module_name) prediction = tree.findDecision(['Sunny', 'Hot', 'High', 'Weak'])

The README frames this as a way to restore already built trees, skip the learning steps, or apply transfer learning. Note the method name difference: the restored object exposes findDecision, not predict. That matters if you are writing a wrapper around the library, because the two code paths have different call signatures.

The second path uses a pickled model:

chef.save_model(model, "model.pkl") model = chef.load_model("model.pkl") prediction = chef.predict(model, ['Sunny',85,85,'Weak'])

Here the README adds a constraint that is easy to skim past: restoration requires storing both the .py and .pkl files under outputs/rules. So the pickle alone is not a self-contained artifact. The generated rule file is part of the runtime state. This is a real coupling to plan around. If you copy model.pkl to another machine without the matching rules module, or you regenerate the rules file with a different config while keeping an old pickle, the pair can disagree. The README does not describe a version or hash check between the two, so the consistency of that pair is your responsibility. Also note the second example mixes types, passing 85 for what look like numeric columns, while the first passes all strings. Keep your prediction argument types aligned with the training frame.

Where ChefBoost is the wrong tool

The categorical-first design is a limitation as much as a feature. Because the algorithm operates on nominal splits, a high-cardinality categorical column, say a user ID or a zip code with thousands of levels, gives the split search a very large space to consider at every node. The README does not describe any cardinality cap, target encoding, or minimum-frequency threshold for categorical levels. On a column with thousands of distinct values, expect the fit to slow down and the resulting rule file to grow into something no human will read, which removes the main reason to choose this library.

There is also no mention of missing value handling anywhere in the supplied material. If your DataFrame has NaN in a numeric column or an empty string in a nominal one, the README gives no guidance on what the split search does with it. Verify this on your own data before trusting the output.

Scale is the other boundary. Nothing in the README describes parallelism, GPU support, out-of-core training, or sparse input. The ensembles are configured in single digits in the examples, five trees for random forest and seven epochs for gradient boosting. Those are teaching-scale numbers. If your problem needs hundreds of boosting rounds over millions of rows, this is not the library for that job, and the README does not claim otherwise.

Finally, the output is not a scikit-learn estimator. There is no fit/predict class implementing the standard interface, so scikit-learn's cross-validation splitters, pipelines and grid search will not drive it directly. For a small experiment that is fine. For a team standardizing on scikit-learn APIs, it is friction you will feel on day two.

How it differs from scikit-learn's tree module

The obvious comparison is scikit-learn's DecisionTreeClassifier and its ensemble classes, and the difference is not accuracy on paper. It is what each library assumes about your input and what each gives back.

scikit-learn expects an encoded numeric matrix. Categorical columns must be handled before training, typically with OneHotEncoder or OrdinalEncoder inside a ColumnTransformer. That gives you a uniform array interface, predictable performance on wide numeric data, and a large ecosystem of tools that consume the estimator. What you get back is a model object with arrays of split thresholds and leaf values, which you inspect through the tree module or convert to text with export_text or export_graphviz.

ChefBoost inverts both halves. It takes the DataFrame with strings intact and gives you back executable Python. The trade is that you lose the estimator protocol and the surrounding tooling. There is no sklearn-compatible wrapper mentioned in the README, so you cannot drop it into a Pipeline and call cross_val_score.

A second comparison point is the algorithm menu itself. scikit-learn implements CART-style trees with GINI or entropy and does not offer ID3, C4.5 or CHAID under those names. If your reason for looking at ChefBoost is that a course, a paper or a specification requires C4.5 gain ratio or CHAID chi-square splitting, scikit-learn will not give you those criteria, and reimplementing them is more work than installing this package. That is the strongest case for the library: not that it beats scikit-learn, but that it covers a set of named algorithms with their textbook splitting metrics intact.

Maintenance, licensing and what to check before you depend on it

The licence is MIT, stated in the README badge and the repository metadata. That permits commercial use, modification and redistribution provided the copyright notice and permission notice are retained. This is a description of the licence text, not legal advice; if you are embedding the library in a distributed product, have your own counsel read the LICENSE file rather than this paragraph.

The repository is not archived and the last push recorded is 2026-05-13, so development has not stopped. No releases were retrieved for this review, which means the version history is something you should check on PyPI yourself before pinning a version in requirements.txt. Pin it, in any case. A library whose output format is generated Python code can change the shape of that code between versions, and your downstream tooling that parses outputs/rules/rules.py will break silently if it does.

On upgrade cost, the README does not describe a deprecation policy or a compatibility guarantee for the generated rule files. Treat the .py output as an internal artifact of the version that produced it, not as a stable interchange format. The practical check before adopting: run the golf example from the README, confirm that outputs/rules/rules.py appears with the nested if structure shown, then save and restore a model and confirm the two prediction paths agree on the same input. That takes a few minutes and tells you whether the persistence story holds on your Python and pandas versions. If it does, you have a readable, dependency-light tree generator for categorical data. If it does not, you have found the failure before it reached a notebook that someone else depends on.

Editorial conclusion

Adopt ChefBoost if your dataset is small to medium, mixes nominal and numeric columns, and you need to hand a human a readable rule file or ship a tree without a model runtime. Do not adopt it if you need GPU training, sparse high-dimensional input, calibrated probabilities, or a scikit-learn-compatible estimator for pipelines and cross-validation tooling. Before committing, verify three things in a scratch directory: that a config with algorithm C4.5 produces outputs/rules/rules.py, that restoreTree on that module plus a matching model.pkl reproduces the same prediction as chef.predict, and that your own categorical columns survive the round trip unchanged. If the .py and .pkl pair drifts, your transfer learning path is broken and you will only find out at inference time.

Official sources

  1. Issues
  2. License: MIT
  3. Project website
  4. README
  5. serengil/chefboost on GitHub
Community notes

Community notes