Skip to content

A scalable general purpose micro-framework for defining dataflows. You can use it to build dataframes, numpy matrices, python objects, ML models, etc. Embed Hamilton anywhere python runs, e.g. spark, airflow, jupyter, fastapi, python scripts, etc.

License

stitchfix/hamilton

main
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Code

Latest commit

 

Git stats

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
January 30, 2023 09:11
October 29, 2022 14:47
January 30, 2023 16:18
January 29, 2023 17:58
January 30, 2023 16:18
September 2, 2021 11:26
January 31, 2023 09:43
January 2, 2023 20:42
April 10, 2022 21:56
January 30, 2023 09:11
September 2, 2021 11:26
September 2, 2021 11:26
January 30, 2023 16:18
October 31, 2022 21:26

Welcome to Hamilton's Github Repository

Hamilton CircleCI Hamilton Slack Twitter
Python supported PyPi Version Total Downloads

Hamilton

The general purpose micro-framework for creating dataflows from python functions!

Specifically, Hamilton defines a novel paradigm, that allows you to specify a flow of (delayed) execution, that forms a Directed Acyclic Graph (DAG). It was originally built to solve creating wide (1000+) column dataframes. Core to the design of Hamilton is a clear mapping of function name to dataflow output. That is, Hamilton forces a certain paradigm with writing functions, and aims for DAG clarity, easy modifications, with always unit testable and naturally documentable code.

For the backstory on how Hamilton came about, see our blog post!.

Getting Started

Here's a quick getting started guide to get you up and running in less than 15 minutes. If you need help join our slack community to chat/ask Qs/etc. For the latest updates, follow us on twitter!

Installation

Requirements:

  • Python 3.7+

To get started, first you need to install hamilton. It is published to pypi under sf-hamilton:

pip install sf-hamilton

Note: to use the DAG visualization functionality, you should instead do:

pip install "sf-hamilton[visualization]"

While it is installing we encourage you to start on the next section.

Note: the content (i.e. names, function bodies) of our example code snippets are for illustrative purposes only, and don't reflect what we actually do internally.

Hamilton in <15 minutes

Hamilton is a new paradigm when it comes to creating, um, dataframes (let's use dataframes as an example, otherwise you can create ANY python object). Rather than thinking about manipulating a central dataframe, as is normal in some data engineering/data science work, you instead think about the column(s) you want to create, and what inputs are required. There is no need for you to think about maintaining this dataframe, meaning you do not need to think about any "glue" code; this is all taken care of by the Hamilton framework.

For example rather than writing the following to manipulate a central dataframe object df:

df['col_c'] = df['col_a'] + df['col_b']

you write

def col_c(col_a: pd.Series, col_b: pd.Series) -> pd.Series:
    """Creating column c from summing column a and column b."""
    return col_a + col_b

In diagram form: example The Hamilton framework will then be able to build a DAG from this function definition.

So let's create a "Hello World" and start using Hamilton!

Your first hello world.

By now, you should have installed Hamilton, so let's write some code.

  1. Create a file my_functions.py and add the following functions:
import pandas as pd

def avg_3wk_spend(spend: pd.Series) -> pd.Series:
    """Rolling 3 week average spend."""
    return spend.rolling(3).mean()

def spend_per_signup(spend: pd.Series, signups: pd.Series) -> pd.Series:
    """The cost per signup in relation to spend."""
    return spend / signups

The astute observer will notice we have not defined spend or signups as functions. That is okay, this just means these need to be provided as input when we come to actually wanting to create a dataframe.

Note: functions can take or create scalar values, in addition to any python object type.

  1. Create a my_script.py which is where code will live to tell Hamilton what to do:
import sys
import logging
import importlib

import pandas as pd
from hamilton import driver

logging.basicConfig(stream=sys.stdout)
initial_columns = {  # load from actuals or wherever -- this is our initial data we use as input.
    # Note: these do not have to be all series, they could be scalar inputs.
    'signups': pd.Series([1, 10, 50, 100, 200, 400]),
    'spend': pd.Series([10, 10, 20, 40, 40, 50]),
}
# we need to tell hamilton where to load function definitions from
module_name = 'my_functions'
module = importlib.import_module(module_name) # or we could just do `import my_functions`
dr = driver.Driver(initial_columns, module)  # can pass in multiple modules
# we need to specify what we want in the final dataframe.
output_columns = [
    'spend',  # or module.spend
    'signups',  # or module.signups
    'avg_3wk_spend',  # or module.avg_3wk_spend
    'spend_per_signup',  # or module.spend_per_signup
]
# let's create the dataframe!
# if you only did `pip install sf-hamilton` earlier:
df = dr.execute(output_columns)
# else if you did `pip install "sf-hamilton[visualization]"` earlier:
# dr.visualize_execution(output_columns, './my-dag.dot', {})
print(df)
  1. Run my_script.py

python my_script.py

You should see the following output:

   spend  signups  avg_3wk_spend  spend_per_signup
0     10        1            NaN            10.000
1     10       10            NaN             1.000
2     20       50      13.333333             0.400
3     40      100      23.333333             0.400
4     40      200      33.333333             0.200
5     50      400      43.333333             0.125

You should see the following image if you ran dr.visualize_execution(output_columns, './my-dag.dot', {}):

hello_world_image

Congratulations - you just created your Hamilton dataflow that created a dataframe!

Example Hamilton Dataflows

We have a growing list of examples showcasing how one might use Hamilton. You can find them all under the examples/ directory. E.g.

Slack Community

We have a small but active community on slack. Come join us!

License

Hamilton is released under the BSD 3-Clause Clear License. If you need to get in touch about something, contact us at algorithms-opensource (at) stitchfix.com.

Used internally by:

To add your company, make a pull request to add it here.

Contributing

We take contributions, large and small. We operate via a Code of Conduct and expect anyone contributing to do the same.

To see how you can contribute, please read our contributing guidelines and then our developer setup guide.

Blog Posts

Videos of talks

Watch the video

Citing Hamilton

We'd appreciate citing Hamilton by referencing one of the following:

@inproceedings{DBLP:conf/vldb/KrawczykI22,
  author    = {Stefan Krawczyk and Elijah ben Izzy},
  editor    = {Satyanarayana R. Valluri and Mohamed Za{\"{\i}}t},
  title     = {Hamilton: a modular open source declarative paradigm for high level
               modeling of dataflows},
  booktitle = {1st International Workshop on Composable Data Management Systems,
               CDMS@VLDB 2022, Sydney, Australia, September 9, 2022},
  year      = {2022},
  url       = {https://cdmsworkshop.github.io/2022/Proceedings/ShortPapers/Paper6\_StefanKrawczyk.pdf},
  timestamp = {Wed, 19 Oct 2022 16:20:48 +0200},
  biburl    = {https://dblp.org/rec/conf/vldb/KrawczykI22.bib},
  bibsource = {dblp computer science bibliography, https://dblp.org}
}

@inproceedings{CEURWS:conf/vldb/KrawczykIQ22,
  author    = {Stefan Krawczyk and Elijah ben Izzy and Danielle Quinn},
  editor    = {Cinzia Cappiello and Sandra Geisler and Maria-Esther Vidal},
  title     = {Hamilton: enabling software engineering best practices for data transformations via generalized dataflow graphs},
  booktitle = {1st International Workshop on Data Ecosystems co-located with 48th International Conference on Very Large Databases (VLDB 2022)},
  pages     = {41--50},
  url       = {https://ceur-ws.org/Vol-3306/paper5.pdf},
  year      = {2022}
}

Prescribed Development Workflow

In general we prescribe the following:

  1. Ensure you understand Hamilton Basics.
  2. Familiarize yourself with some of the Hamilton decorators. They will help keep your code DRY.
  3. Start creating Hamilton Functions that represent your work. We suggest grouping them in modules where it makes sense.
  4. Write a simple script so that you can easily run things end to end.
  5. Join our Slack community to chat/ask Qs/etc.

For the backstory on Hamilton we invite you to watch a roughly-9 minute lightning talk on it that we gave at the apply conference: video, slides.

PyCharm Tips

If you're using Hamilton, it's likely that you'll need to migrate some code. Here are some useful tricks we found to speed up that process.

Live templates

Live templates are a cool feature and allow you to type in a name which expands into some code.

E.g. For example, we wrote one to make it quick to stub out Hamilton functions: typing graphfunc would turn into ->

def _(_: pd.Series) -> pd.Series:
   """"""
   return _

Where the blanks are where you can tab with the cursor and fill things in. See your pycharm preferences for setting this up.

Multiple Cursors

If you are doing a lot of repetitive work, one might consider multiple cursors. Multiple cursors allow you to do things on multiple lines at once.

To use it hit option + mouse click to create multiple cursors. Esc to revert back to a normal mode.

Usage analytics & data privacy

By default, when using Hamilton, it collects anonymous usage data to help improve Hamilton and know where to apply development efforts.

We capture three types of events: one when the Driver object is instantiated, one when the execute() call on the Driver object completes, and one for most Driver object function invocations. No user data or potentially sensitive information is or ever will be collected. The captured data is limited to:

  • Operating System and Python version
  • A persistent UUID to indentify the session, stored in ~/.hamilton.conf.
  • Error stack trace limited to Hamilton code, if one occurs.
  • Information on what features you're using from Hamilton: decorators, adapters, result builders.
  • How Hamilton is being used: number of final nodes in DAG, number of modules, size of objects passed to execute(), the name of the Driver function being invoked.

If you're worried, see telemetry.py for details.

If you do not wish to participate, one can opt-out with one of the following methods:

  1. Set it to false programmatically in your code before creating a Hamilton driver:
    from hamilton import telemetry
    telemetry.disable_telemetry()
  2. Set the key telemetry_enabled to false in ~/.hamilton.conf under the DEFAULT section:
    [DEFAULT]
    telemetry_enabled = False
    
  3. Set HAMILTON_TELEMETRY_ENABLED=false as an environment variable. Either setting it for your shell session:
    export HAMILTON_TELEMETRY_ENABLED=false
    or passing it as part of the run command:
    HAMILTON_TELEMETRY_ENABLED=false python NAME_OF_MY_DRIVER.py

Contributors

Code Contributors

  • Stefan Krawczyk (@skrawcz)
  • Elijah ben Izzy (@elijahbenizzy)
  • Danielle Quinn (@danfisher-sf)
  • Rachel Insoft (@rinsoft-sf)
  • Shelly Jang (@shellyjang)
  • Vincent Chu (@vslchusf)
  • Christopher Prohm (@chmp)
  • James Lamb (@jameslamb)
  • Avnish Pal (@bovem)
  • Sarah Haskins (@frenchfrywpepper)

Bug Hunters/Special Mentions

  • Nils Olsson (@nilsso)
  • Michał Siedlaczek (@elshize)
  • Alaa Abedrabbo (@AAbedrabbo)
  • Shreya Datar (@datarshreya)
  • Baldo Faieta (@baldofaieta)
  • Anwar Brini (@AnwarBrini)