{"id":34030656,"url":"https://github.com/sarthfrey/ensemble","last_synced_at":"2026-04-06T09:01:34.610Z","repository":{"id":57426533,"uuid":"194581146","full_name":"sarthfrey/ensemble","owner":"sarthfrey","description":"Combine models, easily.  🚀","archived":false,"fork":false,"pushed_at":"2019-08-05T08:04:35.000Z","size":274,"stargazers_count":24,"open_issues_count":9,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-09-25T13:54:20.674Z","etag":null,"topics":["ensemble-learning","infrastructure","machine-learning","modeling-tool","python3"],"latest_commit_sha":null,"homepage":"https://ensemble-pkg.readthedocs.io","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sarthfrey.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-07-01T01:43:09.000Z","updated_at":"2025-02-01T03:20:09.000Z","dependencies_parsed_at":"2022-09-19T06:00:28.597Z","dependency_job_id":null,"html_url":"https://github.com/sarthfrey/ensemble","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/sarthfrey/ensemble","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sarthfrey%2Fensemble","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sarthfrey%2Fensemble/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sarthfrey%2Fensemble/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sarthfrey%2Fensemble/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sarthfrey","download_url":"https://codeload.github.com/sarthfrey/ensemble/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sarthfrey%2Fensemble/sbom","scorecard":{"id":800899,"data":{"date":"2025-08-11","repo":{"name":"github.com/sarthfrey/ensemble","commit":"2ecfae3941a560e1dc54b3df0faf0d45420519a6"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}}]},"last_synced_at":"2025-08-23T10:27:10.850Z","repository_id":57426533,"created_at":"2025-08-23T10:27:10.850Z","updated_at":"2025-08-23T10:27:10.850Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31466228,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-06T08:36:52.050Z","status":"ssl_error","status_checked_at":"2026-04-06T08:36:51.267Z","response_time":112,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ensemble-learning","infrastructure","machine-learning","modeling-tool","python3"],"created_at":"2025-12-13T18:02:18.181Z","updated_at":"2026-04-06T09:01:34.601Z","avatar_url":"https://github.com/sarthfrey.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ensemble\n\nCombine models, *easily*.\n\n\u003cimg alt=\"Model Ensemble\" src=\"img/main.png\" width=\"55%\" height=\"55%\"/\u003e\n\n**ensemble** lets you combine your models and access them by a single object. You may use that ensemble to multiplex between your models, call them all, and aggregate the results. You can do *bagging*, *boosting*, *stacking*, and more. You may even create ensembles of ensembles!\n\nThis package borrows the idea of computation graph sessioning from [TensorFlow](https://github.com/tensorflow/tensorflow) and implements the [composite pattern](https://en.wikipedia.org/wiki/Composite_pattern) for building tree hierarchies.\n\n### Documentation\n\n[![Documentation Status](https://readthedocs.org/projects/ensemble-pkg/badge/?version=latest)](https://ensemble-pkg.readthedocs.io/en/latest/?badge=latest)\n\nRead the docs at [ensemble-pkg.readthedocs.io](https://ensemble-pkg.readthedocs.io)\n\n### Installation\n\n```\npip install ensemble-pkg\n```\n\n### Case Study\n\nLet's say we have two models that accomplish a binary classification task. We want to be able to easily combine the two models into an ensemble and then test the precision and recall of both models and the ensemble. With this package, it's easy.\n\nWe start off by building our two models for a dataset. In this case, the task is classifying if a number is divisble by 15. We have two models, one says the number is divisble by 15 if it is divisble by 3 and the other says so if it is divisble by 5.\n\n```python\ndef model1(x):\n  return x % 3 == 0\n\ndef model2(x):\n  return x % 5 == 0\n\ndef get_dataset():\n  return [(i, i % 15 == 0) for i in range(1, 101)]\n```\n\nDefine a function that gets our precision and recall for a given dataset and set of predictions:\n\n```python\ndef get_results(dataset, preds):\n  labels = [label for _, label in dataset]\n  positives = sum(1 for label in labels if label)\n  predicted_positives = sum(1 for pred in preds if pred)\n  true_positives = sum(1 for label, pred in zip(labels, preds) if label and pred)\n  return 100.0 * true_positives / predicted_positives, 100.0 * true_positives / positives\n```\n\nNext we build a model ensemble from `model1` and `model2`, specifically one that only outputs `True` if all its children output `True`. In this case, the ensemble would then only output `True` if the input is both divisible by 3 and 5.\n\n```python\ne = Ensemble('ensemble', children=[model1, model2], mode='all')\ne(x=3) # returns False\ne(x=5) # returns False\ne(x=15) # returns True\n```\n\nFinally, lets build another ensemble from our two models and the ensemble in order to easily aggregate the precision and recall stats for each model. We do this by decorating each child model with an evaluation decorator which modifies the models to take a dataset and output precision and recall, instead of taking a number and outputing `True` or `False`.\n\n```python\ndef evaluate(model):\n  def wrapper(dataset):\n    preds = [model(x=x) for x, _ in dataset]\n    precision, recall = get_results(dataset, preds)\n    return {\n      'precision': f'{precision:.1f}%',\n      'recall': f'{recall:.1f}%',\n    }\n  return wrapper\n\nresults = Ensemble('results', children=[model1, model2, e])\nresults.decorate_children(evaluate)\n```\n\nFinally we run `results(dataset=get_dataset())` and get the following results as expected!\n\n```\n{'ensemble': {'precision': '100.0%', 'recall': '100.0%'},\n 'model1': {'precision': '18.2%', 'recall': '100.0%'},\n 'model2': {'precision': '30.0%', 'recall': '100.0%'}}\n```\n\nIn a few keystrokes, we built the following graph structure.\n\n\u003cimg alt=\"Graph Structure\" src=\"img/graph.png\" width=\"30%\" height=\"30%\"/\u003e\n\n### Examples\n\nDefine your model functions and create your ensemble:\n\n```python\n\u003e\u003e\u003e from ensemble import Ensemble\n\u003e\u003e\u003e def square(x):\n...     return x**2\n\u003e\u003e\u003e def cube(x):\n...     return x**3\n\u003e\u003e\u003e e = Ensemble(name='e1', children=[square, cube])\n```\n\nCall all the models in the ensemble:\n```python\n\u003e\u003e\u003e e(x=2)\n{'square': 4, 'cube': 8}\n\u003e\u003e\u003e e(x=3)\n{'square': 9, 'cube': 27}\n```\n\nMultiplex between functions:\n\n```python\n\u003e\u003e\u003e e.multiplex('square', x=2)\n4\n\u003e\u003e\u003e e.multiplex('cube', x=3)\n27\n```\n\nYou may instead decorate your model functions with `@child` in order to attach them to an ensemble:\n\n```python\n\u003e\u003e\u003e from ensemble import child\n\u003e\u003e\u003e @child('e2')\n... def func1(x):\n...     return x**2\n...\n\u003e\u003e\u003e @child('e2')\n... def func2(x):\n...     return x**3\n...\n\u003e\u003e\u003e e = Ensemble('e2')\n\u003e\u003e\u003e e(x=3)\n{'func1': 9, 'func2': 27}\n```\n\nYou may even attach a model to multiple ensembles!\n\n```python\n\u003e\u003e\u003e @child('e2', 'e3')\n... def func3(x, y):\n...     return x**3 + y\n...\n\u003e\u003e\u003e e2(x=2, y=3)\n{'func1': 4, 'func2': 8, 'func3': 11}\n\u003e\u003e\u003e\n\u003e\u003e\u003e e3 = Ensemble('e3')\n\u003e\u003e\u003e e3(x=2, y=3)\n{'func3': 11}\n```\n\nCompute statstical aggregations from your ensemble's models:\n\n```python\n\u003e\u003e\u003e def a(x):\n...   return x + 1\n...\n\u003e\u003e\u003e def b(y):\n...   return y + 2\n...\n\u003e\u003e\u003e def c(z):\n...   return z + 2\n...\n\u003e\u003e\u003e e = Ensemble('e4', children=[a, b], weights=[3.0, 1.0])\n\u003e\u003e\u003e e.mean(x=2, y=3)\n4.0\n\u003e\u003e\u003e e.weighted_mean(x=2, y=3)\n3.5\n\u003e\u003e\u003e e.weighted_sum(x=2, y=3)\n14.0\n\u003e\u003e\u003e e = Ensemble('e6', [a, b, c])\n\u003e\u003e\u003e e.vote(x=1, y=1, z=1)\n3\n```\n\nBuild ensembles of ensembles!\n\n```python\n\u003e\u003e\u003e first_ensemble = Ensemble('first', children=[c])\n\u003e\u003e\u003e second_ensemble = Ensemble('second', children=[a, b])\n\u003e\u003e\u003e parent_ensemble = Ensemble('parent', children=[first_ensemble, second_ensemble])\n\u003e\u003e\u003e parent_ensemble(x=1, y=1, z=1)\n{'first': {'c': 3}, 'second': {'a': 2, 'b': 3}}\n\u003e\u003e\u003e parent_ensemble.multiplex('second', x=3, y=1)\n{'a': 4, 'b': 3}\n```\n\nUse that idea to chain aggregate computations! Compute the mean of the sum of the model outputs in each ensemble:\n\n```python\n\u003e\u003e\u003e first_ensemble.set_mode('sum')\nEnsemble(name='first', children=['c'], weights=None, mode='sum')\n\u003e\u003e\u003e second_ensemble.set_mode('sum')\nEnsemble(name='second', children=['a', 'b'], weights=None, mode='sum')\n\u003e\u003e\u003e parent_ensemble.mean(x=1, y=1, z=1)\n4.0\n```\n\nIf you forget what models are in your ensemble, just check:\n\n```python\n\u003e\u003e\u003e print(parent_ensemble)\nEnsemble(name='parent', children=['first', 'second'], weights=None, mode='all')\n  Ensemble(name='first', children=['c'], weights=None, mode='sum')\n    Model(name='c', func=c(z))\n  Ensemble(name='second', children=['a', 'b'], weights=None, mode='sum')\n    Model(name='a', func=a(x))\n    Model(name='b', func=b(y))\n```\n\nIn the above example, a tree is shown which shows which models and ensembles are the children of which ensembles!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsarthfrey%2Fensemble","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsarthfrey%2Fensemble","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsarthfrey%2Fensemble/lists"}