YDF (Yggdrasil Decision Forests): A C++ Decision Forest Library With Python, Go and JavaScript Bindings
A library to train, evaluate, interpret, and productionize decision forest models such as Random Forest and Gradient Boosted Decision Trees.
At a glance
- What is it?
- YDF trains, evaluates, interprets and serves Random Forest, Gradient Boosted Trees, CART and Isolation Forest models. The Python package is the low-friction entry point; the C++ core is the reason the project exists, and it is also the reason the install story is not uniform across languages.
- Who is it for?
- Adopt YDF if you need one decision forest implementation that spans Python experimentation and a native C++ serving path, or if you want Isolation Forest and CART alongside GBDT under one learner API. Do not adopt it if your stack is Python-only and scikit-learn already meets your accuracy and latency targets, because you would be trading a familiar estimator interface for a new one with no compensating benefit.
- Can I use it commercially?
- Yes. Apache-2.0 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 received new commits within the last day.
- What is it written in?
- Mainly C++, 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 YDF fills: one forest implementation across Python, C++ and serving
Most teams pick a decision forest library twice. Once in Python, where scikit-learn or XGBoost is the default, and again at serving time, where the model has to be reimplemented or exported into a runtime that may not support the exact tree semantics of the training library. YDF is built around avoiding that second pick. The README describes it as a library to train, evaluate, interpret, and serve Random Forest, Gradient Boosted Decision Trees, CART and Isolation forest models, and the repository ships bindings for C++, Python, Go and JavaScript according to its topic list. The intended user is an engineer who has a model that must run outside a Python process, or a team that wants the same learner configuration available in an experiment notebook and in a production binary. If you only ever call predict inside a Python service, this project's main advantage does not apply to you.
Learner, model, dataset: the object model in the Python API
The Python surface is small and opinionated. You construct a learner by naming the algorithm and the label column, then call train on a dataframe. In the README example that is ydf.GradientBoostedTreesLearner(label="income").train(train_ds), which returns a model object rather than a bare array of trees. Everything else hangs off that object: describe() for input features, training logs and structure; evaluate(test_ds) for metrics such as roc, accuracy, confusion matrix and confidence intervals; predict(test_ds); analyze(test_ds) for partial dependence plots and variable importance; benchmark(test_ds) for inference speed; and save("/tmp/my_model") to write the model to disk. That grouping is the design decision worth noting. Interpretation and benchmarking are methods on the trained model, not separate tooling you assemble, which means the cost of inspecting a model is one call. The trade-off is that you work with YDF's model type rather than a plain estimator, so anything expecting a scikit-learn style fit/predict interface needs an adapter.
The C++ path: data specification, training config, learner, model
The C++ example in the README shows the same pipeline with explicit types. You point at a dataset with a URI-style path such as "csv:train.csv", build a DataSpecification with CreateDataSpec(dataset_path, false, {}, &spec), then fill a TrainingConfig with set_learner("RANDOM_FOREST"), set_task(Task::CLASSIFICATION) and set_label("my_label"). From that config you get an AbstractLearner via GetLearner(train_config, &learner), call learner->Train(dataset_path, spec), and persist with SaveModel("my_model", model.get()). Two things stand out. First, the learner is selected by a string name, so switching from RANDOM_FOREST to a gradient boosted variant is a config change rather than a different class hierarchy. Second, the dataset is referenced by path at train time, not passed as an in-memory matrix, which is what makes the distributed-computing topic plausible: the training entry point does not assume the data fits in the calling process. The README does not document the distributed setup, so treat that as something to confirm in the documentation rather than assume from the topic tag.
Getting it running: pip for Python, a build for C++
The Python install is one line: pip install ydf -U. The README gives no version pins, no optional extras and no platform matrix, so the supported Python versions are something you check on PyPI rather than in the README. The releases listed are pydf_0.16.1 (Python API 0.16.1) from 2026-03-26, pydf_0.16.0 from 2026-03-17 and pydf_0.15.0 from 2026-02-04, which tells you the Python package is versioned separately from the C++ core and that the cadence is roughly monthly. The C++ side has no install command in the README at all; it points to examples/beginner.cc as the source of the sample and to the documentation site for more. If your plan depends on linking YDF into an existing C++ service, the build system, dependencies and supported compilers are the first things to look up, because the README does not answer them. The README also links a usage_example.ipynb Colab notebook and a Getting Started tutorial under ydf.readthedocs.io, which is where the per-algorithm configuration keys presumably live.
Where YDF is the wrong choice
The README's own framing is the limitation. It is a decision forest library, so if your problem is not tabular, or if a linear model or a neural network already wins on your validation set, YDF has nothing to offer you. Within tabular work, the sharper constraint is that the README documents no GPU training path and no automatic hyperparameter search. You configure a learner and train it; tuning is your loop. That is a deliberate scope choice, but it means a team used to a single call that searches a parameter space will be writing that loop itself. The second limitation is documentation asymmetry. The Python example is complete enough to run end to end, with a dataset URL, a label, and every method call spelled out. The C++ example stops at training and saving. Serialization formats, thread-safety guarantees for concurrent predict calls, and the behaviour of the model object across processes are not covered in the README, so anyone serving from C++ is reading the docs site, not this file. Finally, the language bindings beyond Python and C++ are listed as topics but have no usage example here, so their maturity relative to the core is unverified from this material.
YDF against scikit-learn's forests
The obvious alternative is scikit-learn, which also offers random forests and gradient boosting over tabular data. The difference is not the algorithm; it is where the model lives. A scikit-learn forest is a Python object, and moving it into a C++ or Go service means exporting to a format such as ONNX or reimplementing traversal. YDF's pitch is that the trained model is already a C++ artifact, with the Python API as a wrapper over it, so the same saved model can be loaded by a native process. The second difference is scope. Scikit-learn spreads its tree variants across several estimator classes with their own parameter names. YDF puts Random Forest, GBDT, CART and Isolation Forest behind a learner name in a training config, which is a smaller surface to learn if you switch between them. What you give up is ecosystem gravity: scikit-learn pipelines, cross-validation helpers and third-party integrations assume the estimator interface. Choosing YDF means accepting a narrower ecosystem in exchange for a native serving path.
Maintenance, versioning and the Apache-2.0 terms
The repository is not archived and the last push is dated 2026-09-10, so the project is active as of that date. The Python package moves on its own release train, with 0.16.1 following 0.16.0 by nine days, which suggests patch releases arrive quickly but also that the minor version is where behaviour can shift. Pin the version in your requirements file rather than tracking latest if you have models in production, because a model saved by one version and loaded by another is the failure mode you cannot see until predictions change. The licence is Apache-2.0, which permits commercial use, modification and redistribution provided you keep the licence and notices intact and state significant changes; it also includes an explicit patent grant. That is a permissive licence with no copyleft obligation on your own code, but this is a description of the terms, not legal advice. The README asks that scientific publications cite the KDD 2023 paper by Guillame-Bert et al., which is a request rather than a licence condition. For support, the README lists a core team contact address rather than a public issue tracker as the primary channel.
Editorial conclusion
Adopt YDF if you need one decision forest implementation that spans Python experimentation and a native C++ serving path, or if you want Isolation Forest and CART alongside GBDT under one learner API. Do not adopt it if your stack is Python-only and scikit-learn already meets your accuracy and latency targets, because you would be trading a familiar estimator interface for a new one with no compensating benefit. Before committing, verify three things against your own data: that pip install ydf -U resolves for your Python version, that model.save followed by reload reproduces predictions on your feature types, and that the C++ build works on your toolchain if you intend to serve from native code.
Community notes