Cookiecutter Data Science
-Why?
-We often think data analysis is a report, some visualizations and or some insights. While these end products are generated by code, it's easy to focus on making to products look real good and ignore the quality of the code that generates them.
-On top of that, it's no secret that good analyses are often the result of exploration, experimentation, and digging into the data to see what works. This is not a process that lends itself to thinking carefully about the structure of your code or your project beforehand. So, let someone else do that thinking and the setup for you. Here's why:
+A logical, reasonably standardized, but flexible project structure for doing and sharing data science work.
+Why use this project structure?
+We often think of data analysis as just the resulting report, insights, or visualizations. Even though these end products are generated by code, it's easy to focus on making the products look nice and ignore the quality of the code that generates them. While these end products are generally the main event, code quality is still important! And we're not talking about bikeshedding the aesthetics or pedantic formatting standards — it's ultimately about correctness and reproducibility.
+It's no secret that good analyses are often the result of very scattershot and serendipitous explorations, tentative experiments, and rapidly testing what works and what doesn't. That is all part of the process for getting to the good stuff, and there is no magic bullet to turn data exploration into a simple, linear progression. That being said, once started it is not a process that lends itself to thinking carefully about the structure of your code or project layout.
+We think it's a pretty big win all around to let someone else do that up-front thinking and setup for you. Here's why:
Other people will thank you
-A well-defined project structure means that a newcomer can begin to understand an analysis without digging in to extensive documentation. Well organized code is self-documenting and provides a lot of context for your code without much overhead. People will thank you for this because they can:
+++Nobody sits around before creating a new Rails project to figure out where they want to put their views; they just run
+rails newto get a standard project skeleton like everybody else.
A well-defined, standard project structure means that a newcomer can begin to understand an analysis without digging in to extensive documentation. Well organized code is self-documenting and provides a lot of context for your code without much overhead. People will thank you for this because they can:
- Collaborate easily with you on this analysis
- Easily learn from your analysis about the process and the domain
- Feel confident in the conclusions the analysis presents
A consistent project structure means that the
+A consistent project structure means less random searching for what gets called where. A good example of this can be found in web development frameworks like Ruby on Rails, Django, and most others. Nobody sits around before creating a new Rails project to figure out where they want to put their views; they just run rails new to get a standard project skeleton like everybody else. And because the default project structure is reasonably logical and standard across most projects, it takes almost no time at all for somebody who has never seen a particular project to figure out where they would find the various moving parts.
Ideally, that's how it should be when a colleague opens up your data science project.
You will thank you
-Ever tried to reproduce an analysis that you did a few months ago or even a few years ago? You may have written the code, but it's now impossible to decipher whether you should use make_figures.py.old, make_figures_working.py or new_make_figures01.py to get things done. A good project structure encourages practices that make it easier to come back to old work, for example separation of concerns, abstracting analysis as a DAG, and engineering best practices like version control.
Ever tried to reproduce an analysis that you did a few months ago or even a few years ago? You may have written the code, but it's now impossible to decipher whether you should use make_figures.py.old, make_figures_working.py or new_make_figures01.py to get things done. Here are some questions we've learned to ask with a sense of existential dread:
-
+
- Are we supposed to go in and join the "region" column to the data before we get started or did that come from one of the notebooks? +
- Come to think of it, which notebook do we have to run first before running the plotting code: was it "process data" or "clean data"? +
- Where did the shapefiles get downloaded from for the geographic plots you made? +
- Et cetera, times infinity. +
These types of questions are painful and are symptoms of a disorganized project. A good project structure encourages practices that make it easier to come back to old work, for example separation of concerns, abstracting analysis as a DAG, and engineering best practices like version control.
Getting started
-With this in mind, we've created a Cookiecutter Data Science template for projects in Python. Your analysis doesn't have to be in Python, but the template does provide some Python boilerplate that you'd want to remove (exclusively in the src folder).
With this in mind, we've created a data science cookiecutter template for projects in Python. Your analysis doesn't have to be in Python, but the template does provide some Python boilerplate that you'd want to remove (in the src folder for example, and the Sphinx documentation skeleton in docs).
Requirements
- Python 2.7 or 3.5 @@ -221,12 +236,12 @@
- @@ -267,6 +282,8 @@ from preprocess.build_features import remove_invalid_data
- If you find you need to install another package, run
pip freeze >> requirements.txtagain and commit the changes to version control.
Opinions
-There are some opinions implicit in the project structure that have grown out of learning what works and what doesn't when collaborating on data science projects. Some of the opinions are about workflows, and some of the opinions are about tools that make life easier. Here are some of the beliefs which this project is built on--if you've got thoughts, please contribute or share them.
+There are some opinions implicit in the project structure that have grown out of our experience with what works and what doesn't when collaborating on data science projects. Some of the opinions are about workflows, and some of the opinions are about tools that make life easier. Here are some of the beliefs which this project is built on—if you've got thoughts, please contribute or share them.
Data is immutable
-Don't edit your raw data in Excel. Don't overwrite your raw data. Don't save multiple versions of the raw data. Treat the data (and its format) as immutable. The code you write should move the raw data through a pipeline to your final analysis. You shouldn't have to run all of the steps every time you want to make a new figure (q.v. Analysis is a DAG), but anyone should be able to reproduce the final products with only the code in src and the data in data/raw.
Also, if data is immutable, it doesn't need source control in the same way that code does. Therefore, by default, the data folder is included in the .gitignore file. If you have a small amount of data that rarely changes, you may want to include the data in the repository. Github currently warns if files are over 50MB and rejects files over 100MB. Some other options for storing/syncing large data include AWS S3 with a syncing tool (e.g., s3cmd), Git Large File Storage, Git Annex, and dat. Currently by default, we ask for an S3 bucket and use s3cmd to sync data in the data folder with the server.
Notebooks are for exploration
-Notebooks such as the Jupyter notebook and other literate programming tools are very effective for exploratory data analysis. However, these tools can be less effective for reproducing an analysis. When we use notebooks in our work, we often subdivide the notebooks folder. For example, notebooks/exploratory contains initial explorations, whereas notebooks/reports is more polished work that can be exported as html to the reports directory.
Don't ever edit your raw data, especially not manually, and especially not in Excel. Don't overwrite your raw data. Don't save multiple versions of the raw data. Treat the data (and its format) as immutable. The code you write should move the raw data through a pipeline to your final analysis. You shouldn't have to run all of the steps every time you want to make a new figure (see Analysis is a DAG), but anyone should be able to reproduce the final products with only the code in src and the data in data/raw.
Also, if data is immutable, it doesn't need source control in the same way that code does. Therefore, by default, the data folder is included in the .gitignore file. If you have a small amount of data that rarely changes, you may want to include the data in the repository. Github currently warns if files are over 50MB and rejects files over 100MB. Some other options for storing/syncing large data include AWS S3 with a syncing tool (e.g., s3cmd), Git Large File Storage, Git Annex, and dat. Currently by default, we ask for an S3 bucket and use s3cmd to sync data in the data folder with the server.
Notebooks are for exploration and communication
+Notebook packages like the Jupyter notebook, Beaker notebook, Zeppelin, and other literate programming tools are very effective for exploratory data analysis. However, these tools can be less effective for reproducing an analysis. When we use notebooks in our work, we often subdivide the notebooks folder. For example, notebooks/exploratory contains initial explorations, whereas notebooks/reports is more polished work that can be exported as html to the reports directory.
Since notebooks are challenging objects for source control (e.g., diffs of the json are often not human-readable and merging is near impossible), we recommended not collaborating directly with others on Jupyter notebooks. There are two steps we recommend for using notebooks effectively:
If you have more complex requirements for recreating your environment, consider a virtual machine based approach such as Docker or Vagrant. Both of these tools use text-based formats (Dockerfile and Vagrantfile respectively) you can easily add to source control to describe how to create a virtual machine with the requirements you need.
+Keep secrets out of version control
+You really don't want to leak your AWS secret key or Postgres username and password on Github. Enough said, mostly — see the Twelve Factor App principles on this point. We generally use a .env file that, thanks to the .gitignore, never makes it into the repository (secrets should be shared via other means with contributors). The .env file defines secrets as environment variables, and is read in automatically by a package like dotenv in Python.
Contributing
The Cookiecutter Data Science project is opinionated, but not afraid to be wrong. Best practices change, tools evolve, and lessons are learned. The goal of this project is to make it easier to start, structure, and share an analysis. Pull requests and filing issues is encouraged. We'd love to hear what works for you, and what doesn't.
If you use the Cookiecutter Data Science project, link back to this page or give us a holler and let us know!
@@ -333,5 +350,5 @@ from preprocess.build_features import remove_invalid_data diff --git a/mkdocs/search_index.json b/mkdocs/search_index.json index 1f6f942..b5ab33a 100644 --- a/mkdocs/search_index.json +++ b/mkdocs/search_index.json @@ -2,32 +2,32 @@ "docs": [ { "location": "/", - "text": "Cookiecutter Data Science\n\n\nWhy?\n\n\nWe often think data analysis is a report, some visualizations and or some insights. While these end products are generated by code, it's easy to focus on making to products look \nreal good\n and ignore the quality of the code that generates them.\n\n\nOn top of that, it's no secret that good analyses are often the result of exploration, experimentation, and digging into the data to see what works. This is not a process that lends itself to thinking carefully about the structure of your code or your project beforehand. So, let someone else do that thinking and the setup for you. Here's why:\n\n\nOther people will thank you\n\n\nA well-defined project structure means that a newcomer can begin to understand an analysis without digging in to extensive documentation. Well organized code is self-documenting and provides a lot of context for your code without much overhead. People will thank you for this because they can:\n\n\n\n\nCollaborate easily with you on this analysis\n\n\nEasily learn from your analysis about the process and the domain\n\n\nFeel confident in the conclusions the analysis presents\n\n\n\n\nA consistent project structure means that the\n\n\nYou will thank you\n\n\nEver tried to reproduce an analysis that you did a few months ago or even a few years ago? You may have written the code, but it's now impossible to decipher whether you should use \nmake_figures.py.old\n, \nmake_figures_working.py\n or \nnew_make_figures01.py\n to get things done. A good project structure encourages practices that make it easier to come back to old work, for example separation of concerns, abstracting analysis as a \nDAG\n, and engineering best practices like version control.\n\n\nGetting started\n\n\nWith this in mind, we've created a Cookiecutter Data Science template for projects in Python. Your analysis doesn't have to be in Python, but the template does provide some Python boilerplate that you'd want to remove (exclusively in the \nsrc\n folder).\n\n\nRequirements\n\n\n\n\nPython 2.7 or 3.5\n\n\ncookiecutter Python package\n \n= 1.4.0: \npip install cookiecutter\n\n\n\n\nStarting a new project\n\n\nStarting a new project is as easy as running this command at the command line. No need to create a directory first, the cookiecutter will do it for you.\n\n\ncookiecutter https://github.com/drivendata/cookiecutter-data-science\n\n\n\nExample\n\n\n\n\n\nDirectory structure\n\n\n\u251c\u2500\u2500 LICENSE\n\u251c\u2500\u2500 Makefile \n- Makefile with commands like `make data` or `make train`\n\u251c\u2500\u2500 README.md \n- The top-level README for developers using this project.\n\u251c\u2500\u2500 data\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 external \n- Data from third party sources.\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 interim \n- Intermediate data that has been transformed.\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 processed \n- The final, canonical data sets for modeling.\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 raw \n- The original, immutable data dump.\n\u2502\n\u251c\u2500\u2500 docs \n- A default Sphinx project; see sphinx-doc.org for details\n\u2502\n\u251c\u2500\u2500 models \n- trained and serialized models, model predictions, or model summaries\n\u2502\n\u251c\u2500\u2500 notebooks \n- Jupyter notebooks. Naming convention is a number (for ordering),\n\u2502 the creator's initials, and a short `-` delimited description, e.g.\n\u2502 `1.0-jqp-initial-data-exploration`.\n\u2502\n\u251c\u2500\u2500 references \n- Data dictionaries, manuals, and all other explanatory materials.\n\u2502\n\u251c\u2500\u2500 reports \n- Generated analysis as HTML, PDF, LaTeX, etc.\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 figures \n- Generated graphics and figures to be used in reporting\n\u2502\n\u251c\u2500\u2500 requirements.txt \n- The requirements file for reproducing the analysis environment, e.g.\n\u2502 generated with `pip freeze \n requirements.txt`\n\u2502\n\u251c\u2500\u2500 src \n- Source code for use in this project.\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 __init__.py \n- Makes src a Python module\n\u2502 \u2502\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 data \n- Scripts to download or generate data\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 make_dataset.py\n\u2502 \u2502\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 features \n- Scripts to turn raw data into features for modeling\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 build_features.py\n\u2502 \u2502\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 models \n- scripts to train models and then use trained models to make\n\u2502 \u2502 predictions\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 predict_model.py\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 train_model.py\n\u2502\n\u2514\u2500\u2500 tox.ini \n- tox file with settings for running tox; see tox.testrun.org\n\n\n\n\nOpinions\n\n\nThere are some opinions implicit in the project structure that have grown out of learning what works and what doesn't when collaborating on data science projects. Some of the opinions are about workflows, and some of the opinions are about tools that make life easier. Here are some of the beliefs which this project is built on--if you've got thoughts, please \ncontribute or share them\n.\n\n\nData is immutable\n\n\nDon't edit your raw data in Excel. Don't overwrite your raw data. Don't save multiple versions of the raw data. Treat the data (and its format) as immutable. The code you write should move the raw data through a pipeline to your final analysis. You shouldn't have to run all of the steps every time you want to make a new figure (q.v. \nAnalysis is a DAG\n), but anyone should be able to reproduce the final products with only the code in \nsrc\n and the data in \ndata/raw\n.\n\n\nAlso, if data is immutable, it doesn't need source control in the same way that code does. Therefore, \nby default, the data folder is included in the .gitignore file.\n If you have a small amount of data that rarely changes, you may want to include the data in the repository. Github currently warns if files are over 50MB and rejects files over 100MB. Some other options for storing/syncing large data include \nAWS S3\n with a syncing tool (e.g., \ns3cmd\n), \nGit Large File Storage\n, \nGit Annex\n, and \ndat\n. Currently by default, we ask for an S3 bucket and use \ns3cmd\n to sync data in the \ndata\n folder with the server.\n\n\nNotebooks are for exploration\n\n\nNotebooks such as the \nJupyter notebook\n and other literate programming tools are very effective for exploratory data analysis. However, these tools can be less effective for reproducing an analysis. When we use notebooks in our work, we often subdivide the \nnotebooks\n folder. For example, \nnotebooks/exploratory\n contains initial explorations, whereas \nnotebooks/reports\n is more polished work that can be exported as html to the \nreports\n directory.\n\n\nSince notebooks are challenging objects for source control (e.g., diffs of the \njson\n are often not human-readable and merging is near impossible), we recommended not collaborating directly with others on Jupyter notebooks. There are two steps we recommend for using notebooks effectively:\n\n\n\n\n\n\nFollow a naming convention that shows the owner and the order the analysis was done in. We use the format \nstep\n-\nghuser\n-\ndescription\n.ipynb\n (e.g., \n0.3-bull-visualize-distributions.ipynb\n).\n\n\n\n\n\n\nRefactor the good parts. Don't write code to do the same task in multiple notebooks. If it's a data preprocessing task, put it in the pipeline at \nsrc/data/make_dataset.py\n and load data from \ndata/interim\n. If it's useful utility code, refactor it to \nsrc\n and import it into notebooks with a cell like the following. If updating the system path is icky to you, we'd recommend making a Python package (there is a \ncookiecutter for that\n as well) and installing that as an editable package with \npip install -e\n.\n\n\n\n\n\n\n# Load the \nautoreload\n extension\n%load_ext autoreload\n\n# always reload modules marked with \n%aimport\n\n%autoreload 1\n\nimport os\nimport sys\n\n# add the 'src' directory as one where we can import modules\nsrc_dir = os.path.join(os.getcwd(), os.pardir, 'src')\nsys.path.append(src_dir)\n\n# import my method from the source code\n%aimport preprocess.build_features\nfrom preprocess.build_features import remove_invalid_data\n\n\n\n\nAnalysis is a DAG\n\n\nOften in an analysis you have long-running steps that preprocesses data or trains models. If these steps have been run already (and you have stored the output somewhere like the \ndata/interim\n directory), you don't want to wait to rerun them every time. We prefer \nmake\n for managing steps that depend on each other, especially the long-running ones. Make is a common tool on unix platforms (and \nis available for Windows\n). Following the \nmake\n documentation\n, \nMakefile conventions\n, and \nportability guide\n will help ensure your Makefiles work effectively across systems. Here are \nsome\n \nexamples\n to \nget started\n.\n\n\nThere are other tools for managing DAGs that are written in Python instead of a DSL (e.g., \nPaver\n, \nLuigi\n, \nAirflow\n, \nSnakemake\n, \nRuffus\n, or \nJoblib\n). Feel free to use these if they are more appropriate for your analysis.\n\n\nBuild from the environment up\n\n\nThe first step in reproducing an analysis is always reproducing the computational environment it was run in. You need the same tools, the same libraries, and the same versions to make everything play nicely together.\n\n\nOne effective approach to this is use \nvirtualenv\n (we recommend \nvirtualenvwrapper\n for managing virtualenvs). By listing all of your requirements in the repository (we include a \nrequirements.txt\n file) you can easily track the packages needed to recreate the analysis. Here is a good workflow:\n\n\n\n\nRun \nmkvirtualenv\n when creating a new project\n\n\npip install\n the packages that your analysis needs\n\n\nRun \npip freeze \n requirements.txt\n to pin the exact package versions used to recreate the analysis\n\n\nIf you find you need to install another package, run \npip freeze \n requirements.txt\n again and commit the changes to version control.\n\n\n\n\nIf you have more complex requirements for recreating your environment, consider a virtual machine based approach such as \nDocker\n or \nVagrant\n. Both of these tools use text-based formats (Dockerfile and Vagrantfile respectively) you can easily add to source control to describe how to create a virtual machine with the requirements you need.\n\n\nContributing\n\n\nThe Cookiecutter Data Science project is opinionated, but not afraid to be wrong. Best practices change, tools evolve, and lessons are learned. \nThe goal of this project is to make it easier to start, structure, and share an analysis.\n \nPull requests\n and \nfiling issues\n is encouraged. We'd love to hear what works for you, and what doesn't.\n\n\nIf you use the Cookiecutter Data Science project, link back to this page or \ngive us a holler\n and \nlet us know\n!\n\n\nLinks to related projects and references\n\n\nProject structure and reproducibility is talked about more in the R research community. Here are some projects and blog posts if you're working in R that may help you out.\n\n\n\n\nProject Template\n - An R data analysis template\n\n\n\"\nDesigning projects\n\" on Nice R Code\n\n\n\"\nMy research workflow\n\" on Carlboettifer.info\n\n\n\"\nA Quick Guide to Organizing Computational Biology Projects\n\" in PLOS Computational Biology\n\n\n\n\nFinally, a huge thanks to the \nCookiecutter\n project (\ngithub\n), which is helping us all spend less time thinking about and writing boilerplate and more time getting things done.", + "text": "Cookiecutter Data Science\n\n\nA logical, reasonably standardized, but flexible project structure for doing and sharing data science work.\n\n\nWhy use this project structure?\n\n\nWe often think of data analysis as just the resulting report, insights, or visualizations. Even though these end products are generated by code, it's easy to focus on making the products \nlook nice\n and ignore the \nquality of the code that generates them\n. While these end products are generally the main event, \ncode quality is still important\n! And we're not talking about bikeshedding the aesthetics or pedantic formatting standards \u2014 it's ultimately about correctness and reproducibility.\n\n\nIt's no secret that good analyses are often the result of very scattershot and serendipitous explorations, tentative experiments, and rapidly testing what works and what doesn't. That is all part of the process for getting to the good stuff, and there is no magic bullet to turn data exploration into a simple, linear progression. That being said, once started it is not a process that lends itself to thinking carefully about the structure of your code or project layout.\n\n\nWe think it's a pretty big win all around to let someone else do that up-front thinking and setup for you. Here's why:\n\n\nOther people will thank you\n\n\n\n\nNobody sits around before creating a new Rails project to figure out where they want to put their views; they just run \nrails new\n to get a standard project skeleton like everybody else.\n\n\n\n\nA well-defined, standard project structure means that a newcomer can begin to understand an analysis without digging in to extensive documentation. Well organized code is self-documenting and provides a lot of context for your code without much overhead. People will thank you for this because they can:\n\n\n\n\nCollaborate easily with you on this analysis\n\n\nEasily learn from your analysis about the process and the domain\n\n\nFeel confident in the conclusions the analysis presents\n\n\n\n\nA consistent project structure means less random searching for what gets called where. A good example of this can be found in web development frameworks like Ruby on Rails, Django, and most others. Nobody sits around before creating a new Rails project to figure out where they want to put their views; they just run \nrails new\n to get a standard project skeleton like everybody else. And because the default project structure is \nreasonably logical\n and \nstandard across most projects\n, it takes almost no time at all for somebody who has never seen a particular project to figure out where they would find the various moving parts.\n\n\nIdeally, that's how it should be when a colleague opens up your data science project.\n\n\nYou will thank you\n\n\nEver tried to reproduce an analysis that you did a few months ago or even a few years ago? You may have written the code, but it's now impossible to decipher whether you should use \nmake_figures.py.old\n, \nmake_figures_working.py\n or \nnew_make_figures01.py\n to get things done. Here are some questions we've learned to ask with a sense of existential dread:\n\n\n\n\nAre we supposed to go in and join the \"region\" column to the data before we get started or did that come from one of the notebooks?\n\n\nCome to think of it, which notebook do we have to run first before running the plotting code: was it \"process data\" or \"clean data\"?\n\n\nWhere did the shapefiles get downloaded from for the geographic plots you made?\n\n\nEt cetera, times infinity.\n\n\n\n\nThese types of questions are painful and are symptoms of a disorganized project. A good project structure encourages practices that make it easier to come back to old work, for example separation of concerns, abstracting analysis as a \nDAG\n, and engineering best practices like version control.\n\n\nGetting started\n\n\nWith this in mind, we've created a data science cookiecutter template for projects in Python. Your analysis doesn't have to be in Python, but the template does provide some Python boilerplate that you'd want to remove (in the \nsrc\n folder for example, and the Sphinx documentation skeleton in \ndocs\n).\n\n\nRequirements\n\n\n\n\nPython 2.7 or 3.5\n\n\ncookiecutter Python package\n \n= 1.4.0: \npip install cookiecutter\n\n\n\n\nStarting a new project\n\n\nStarting a new project is as easy as running this command at the command line. No need to create a directory first, the cookiecutter will do it for you.\n\n\ncookiecutter https://github.com/drivendata/cookiecutter-data-science\n\n\n\nExample\n\n\n\n\n\nDirectory structure\n\n\n\u251c\u2500\u2500 LICENSE\n\u251c\u2500\u2500 Makefile \n- Makefile with commands like `make data` or `make train`\n\u251c\u2500\u2500 README.md \n- The top-level README for developers using this project.\n\u251c\u2500\u2500 data\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 external \n- Data from third party sources.\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 interim \n- Intermediate data that has been transformed.\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 processed \n- The final, canonical data sets for modeling.\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 raw \n- The original, immutable data dump.\n\u2502\n\u251c\u2500\u2500 docs \n- A default Sphinx project; see sphinx-doc.org for details\n\u2502\n\u251c\u2500\u2500 models \n- trained and serialized models, model predictions, or model summaries\n\u2502\n\u251c\u2500\u2500 notebooks \n- Jupyter notebooks. Naming convention is a number (for ordering),\n\u2502 the creator's initials, and a short `-` delimited description, e.g.\n\u2502 `1.0-jqp-initial-data-exploration`.\n\u2502\n\u251c\u2500\u2500 references \n- Data dictionaries, manuals, and all other explanatory materials.\n\u2502\n\u251c\u2500\u2500 reports \n- Generated analysis as HTML, PDF, LaTeX, etc.\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 figures \n- Generated graphics and figures to be used in reporting\n\u2502\n\u251c\u2500\u2500 requirements.txt \n- The requirements file for reproducing the analysis environment, e.g.\n\u2502 generated with `pip freeze \n requirements.txt`\n\u2502\n\u251c\u2500\u2500 src \n- Source code for use in this project.\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 __init__.py \n- Makes src a Python module\n\u2502 \u2502\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 data \n- Scripts to download or generate data\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 make_dataset.py\n\u2502 \u2502\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 features \n- Scripts to turn raw data into features for modeling\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 build_features.py\n\u2502 \u2502\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 models \n- scripts to train models and then use trained models to make\n\u2502 \u2502 predictions\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 predict_model.py\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 train_model.py\n\u2502\n\u2514\u2500\u2500 tox.ini \n- tox file with settings for running tox; see tox.testrun.org\n\n\n\n\nOpinions\n\n\nThere are some opinions implicit in the project structure that have grown out of our experience with what works and what doesn't when collaborating on data science projects. Some of the opinions are about workflows, and some of the opinions are about tools that make life easier. Here are some of the beliefs which this project is built on\u2014if you've got thoughts, please \ncontribute or share them\n.\n\n\nData is immutable\n\n\nDon't ever edit your raw data, especially not manually, and especially not in Excel. Don't overwrite your raw data. Don't save multiple versions of the raw data. Treat the data (and its format) as immutable. The code you write should move the raw data through a pipeline to your final analysis. You shouldn't have to run all of the steps every time you want to make a new figure (see \nAnalysis is a DAG\n), but anyone should be able to reproduce the final products with only the code in \nsrc\n and the data in \ndata/raw\n.\n\n\nAlso, if data is immutable, it doesn't need source control in the same way that code does. Therefore, \nby default, the data folder is included in the \n.gitignore\n file.\n If you have a small amount of data that rarely changes, you may want to include the data in the repository. Github currently warns if files are over 50MB and rejects files over 100MB. Some other options for storing/syncing large data include \nAWS S3\n with a syncing tool (e.g., \ns3cmd\n), \nGit Large File Storage\n, \nGit Annex\n, and \ndat\n. Currently by default, we ask for an S3 bucket and use \ns3cmd\n to sync data in the \ndata\n folder with the server.\n\n\nNotebooks are for exploration and communication\n\n\nNotebook packages like the \nJupyter notebook\n, \nBeaker notebook\n, \nZeppelin\n, and other literate programming tools are very effective for exploratory data analysis. However, these tools can be less effective for reproducing an analysis. When we use notebooks in our work, we often subdivide the \nnotebooks\n folder. For example, \nnotebooks/exploratory\n contains initial explorations, whereas \nnotebooks/reports\n is more polished work that can be exported as html to the \nreports\n directory.\n\n\nSince notebooks are challenging objects for source control (e.g., diffs of the \njson\n are often not human-readable and merging is near impossible), we recommended not collaborating directly with others on Jupyter notebooks. There are two steps we recommend for using notebooks effectively:\n\n\n\n\n\n\nFollow a naming convention that shows the owner and the order the analysis was done in. We use the format \nstep\n-\nghuser\n-\ndescription\n.ipynb\n (e.g., \n0.3-bull-visualize-distributions.ipynb\n).\n\n\n\n\n\n\nRefactor the good parts. Don't write code to do the same task in multiple notebooks. If it's a data preprocessing task, put it in the pipeline at \nsrc/data/make_dataset.py\n and load data from \ndata/interim\n. If it's useful utility code, refactor it to \nsrc\n and import it into notebooks with a cell like the following. If updating the system path is icky to you, we'd recommend making a Python package (there is a \ncookiecutter for that\n as well) and installing that as an editable package with \npip install -e\n.\n\n\n\n\n\n\n# Load the \nautoreload\n extension\n%load_ext autoreload\n\n# always reload modules marked with \n%aimport\n\n%autoreload 1\n\nimport os\nimport sys\n\n# add the 'src' directory as one where we can import modules\nsrc_dir = os.path.join(os.getcwd(), os.pardir, 'src')\nsys.path.append(src_dir)\n\n# import my method from the source code\n%aimport preprocess.build_features\nfrom preprocess.build_features import remove_invalid_data\n\n\n\n\nAnalysis is a DAG\n\n\nOften in an analysis you have long-running steps that preprocesses data or trains models. If these steps have been run already (and you have stored the output somewhere like the \ndata/interim\n directory), you don't want to wait to rerun them every time. We prefer \nmake\n for managing steps that depend on each other, especially the long-running ones. Make is a common tool on unix platforms (and \nis available for Windows\n). Following the \nmake\n documentation\n, \nMakefile conventions\n, and \nportability guide\n will help ensure your Makefiles work effectively across systems. Here are \nsome\n \nexamples\n to \nget started\n.\n\n\nThere are other tools for managing DAGs that are written in Python instead of a DSL (e.g., \nPaver\n, \nLuigi\n, \nAirflow\n, \nSnakemake\n, \nRuffus\n, or \nJoblib\n). Feel free to use these if they are more appropriate for your analysis.\n\n\nBuild from the environment up\n\n\nThe first step in reproducing an analysis is always reproducing the computational environment it was run in. You need the same tools, the same libraries, and the same versions to make everything play nicely together.\n\n\nOne effective approach to this is use \nvirtualenv\n (we recommend \nvirtualenvwrapper\n for managing virtualenvs). By listing all of your requirements in the repository (we include a \nrequirements.txt\n file) you can easily track the packages needed to recreate the analysis. Here is a good workflow:\n\n\n\n\nRun \nmkvirtualenv\n when creating a new project\n\n\npip install\n the packages that your analysis needs\n\n\nRun \npip freeze \n requirements.txt\n to pin the exact package versions used to recreate the analysis\n\n\nIf you find you need to install another package, run \npip freeze \n requirements.txt\n again and commit the changes to version control.\n\n\n\n\nIf you have more complex requirements for recreating your environment, consider a virtual machine based approach such as \nDocker\n or \nVagrant\n. Both of these tools use text-based formats (Dockerfile and Vagrantfile respectively) you can easily add to source control to describe how to create a virtual machine with the requirements you need.\n\n\nKeep secrets out of version control\n\n\nYou \nreally\n don't want to leak your AWS secret key or Postgres username and password on Github. Enough said, mostly \u2014 see the \nTwelve Factor App\n principles on this point. We generally use a \n.env\n file that, thanks to the \n.gitignore\n, never makes it into the repository (secrets should be shared via other means with contributors). The \n.env\n file defines secrets as environment variables, and is read in automatically by a package like \ndotenv\n in Python.\n\n\nContributing\n\n\nThe Cookiecutter Data Science project is opinionated, but not afraid to be wrong. Best practices change, tools evolve, and lessons are learned. \nThe goal of this project is to make it easier to start, structure, and share an analysis.\n \nPull requests\n and \nfiling issues\n is encouraged. We'd love to hear what works for you, and what doesn't.\n\n\nIf you use the Cookiecutter Data Science project, link back to this page or \ngive us a holler\n and \nlet us know\n!\n\n\nLinks to related projects and references\n\n\nProject structure and reproducibility is talked about more in the R research community. Here are some projects and blog posts if you're working in R that may help you out.\n\n\n\n\nProject Template\n - An R data analysis template\n\n\n\"\nDesigning projects\n\" on Nice R Code\n\n\n\"\nMy research workflow\n\" on Carlboettifer.info\n\n\n\"\nA Quick Guide to Organizing Computational Biology Projects\n\" in PLOS Computational Biology\n\n\n\n\nFinally, a huge thanks to the \nCookiecutter\n project (\ngithub\n), which is helping us all spend less time thinking about and writing boilerplate and more time getting things done.", "title": "Home" }, { "location": "/#cookiecutter-data-science", - "text": "", + "text": "A logical, reasonably standardized, but flexible project structure for doing and sharing data science work.", "title": "Cookiecutter Data Science" }, { - "location": "/#why", - "text": "We often think data analysis is a report, some visualizations and or some insights. While these end products are generated by code, it's easy to focus on making to products look real good and ignore the quality of the code that generates them. On top of that, it's no secret that good analyses are often the result of exploration, experimentation, and digging into the data to see what works. This is not a process that lends itself to thinking carefully about the structure of your code or your project beforehand. So, let someone else do that thinking and the setup for you. Here's why:", - "title": "Why?" + "location": "/#why-use-this-project-structure", + "text": "We often think of data analysis as just the resulting report, insights, or visualizations. Even though these end products are generated by code, it's easy to focus on making the products look nice and ignore the quality of the code that generates them . While these end products are generally the main event, code quality is still important ! And we're not talking about bikeshedding the aesthetics or pedantic formatting standards \u2014 it's ultimately about correctness and reproducibility. It's no secret that good analyses are often the result of very scattershot and serendipitous explorations, tentative experiments, and rapidly testing what works and what doesn't. That is all part of the process for getting to the good stuff, and there is no magic bullet to turn data exploration into a simple, linear progression. That being said, once started it is not a process that lends itself to thinking carefully about the structure of your code or project layout. We think it's a pretty big win all around to let someone else do that up-front thinking and setup for you. Here's why:", + "title": "Why use this project structure?" }, { "location": "/#other-people-will-thank-you", - "text": "A well-defined project structure means that a newcomer can begin to understand an analysis without digging in to extensive documentation. Well organized code is self-documenting and provides a lot of context for your code without much overhead. People will thank you for this because they can: Collaborate easily with you on this analysis Easily learn from your analysis about the process and the domain Feel confident in the conclusions the analysis presents A consistent project structure means that the", + "text": "Nobody sits around before creating a new Rails project to figure out where they want to put their views; they just run rails new to get a standard project skeleton like everybody else. A well-defined, standard project structure means that a newcomer can begin to understand an analysis without digging in to extensive documentation. Well organized code is self-documenting and provides a lot of context for your code without much overhead. People will thank you for this because they can: Collaborate easily with you on this analysis Easily learn from your analysis about the process and the domain Feel confident in the conclusions the analysis presents A consistent project structure means less random searching for what gets called where. A good example of this can be found in web development frameworks like Ruby on Rails, Django, and most others. Nobody sits around before creating a new Rails project to figure out where they want to put their views; they just run rails new to get a standard project skeleton like everybody else. And because the default project structure is reasonably logical and standard across most projects , it takes almost no time at all for somebody who has never seen a particular project to figure out where they would find the various moving parts. Ideally, that's how it should be when a colleague opens up your data science project.", "title": "Other people will thank you" }, { "location": "/#you-will-thank-you", - "text": "Ever tried to reproduce an analysis that you did a few months ago or even a few years ago? You may have written the code, but it's now impossible to decipher whether you should use make_figures.py.old , make_figures_working.py or new_make_figures01.py to get things done. A good project structure encourages practices that make it easier to come back to old work, for example separation of concerns, abstracting analysis as a DAG , and engineering best practices like version control.", + "text": "Ever tried to reproduce an analysis that you did a few months ago or even a few years ago? You may have written the code, but it's now impossible to decipher whether you should use make_figures.py.old , make_figures_working.py or new_make_figures01.py to get things done. Here are some questions we've learned to ask with a sense of existential dread: Are we supposed to go in and join the \"region\" column to the data before we get started or did that come from one of the notebooks? Come to think of it, which notebook do we have to run first before running the plotting code: was it \"process data\" or \"clean data\"? Where did the shapefiles get downloaded from for the geographic plots you made? Et cetera, times infinity. These types of questions are painful and are symptoms of a disorganized project. A good project structure encourages practices that make it easier to come back to old work, for example separation of concerns, abstracting analysis as a DAG , and engineering best practices like version control.", "title": "You will thank you" }, { "location": "/#getting-started", - "text": "With this in mind, we've created a Cookiecutter Data Science template for projects in Python. Your analysis doesn't have to be in Python, but the template does provide some Python boilerplate that you'd want to remove (exclusively in the src folder).", + "text": "With this in mind, we've created a data science cookiecutter template for projects in Python. Your analysis doesn't have to be in Python, but the template does provide some Python boilerplate that you'd want to remove (in the src folder for example, and the Sphinx documentation skeleton in docs ).", "title": "Getting started" }, { @@ -52,18 +52,18 @@ }, { "location": "/#opinions", - "text": "There are some opinions implicit in the project structure that have grown out of learning what works and what doesn't when collaborating on data science projects. Some of the opinions are about workflows, and some of the opinions are about tools that make life easier. Here are some of the beliefs which this project is built on--if you've got thoughts, please contribute or share them .", + "text": "There are some opinions implicit in the project structure that have grown out of our experience with what works and what doesn't when collaborating on data science projects. Some of the opinions are about workflows, and some of the opinions are about tools that make life easier. Here are some of the beliefs which this project is built on\u2014if you've got thoughts, please contribute or share them .", "title": "Opinions" }, { "location": "/#data-is-immutable", - "text": "Don't edit your raw data in Excel. Don't overwrite your raw data. Don't save multiple versions of the raw data. Treat the data (and its format) as immutable. The code you write should move the raw data through a pipeline to your final analysis. You shouldn't have to run all of the steps every time you want to make a new figure (q.v. Analysis is a DAG ), but anyone should be able to reproduce the final products with only the code in src and the data in data/raw . Also, if data is immutable, it doesn't need source control in the same way that code does. Therefore, by default, the data folder is included in the .gitignore file. If you have a small amount of data that rarely changes, you may want to include the data in the repository. Github currently warns if files are over 50MB and rejects files over 100MB. Some other options for storing/syncing large data include AWS S3 with a syncing tool (e.g., s3cmd ), Git Large File Storage , Git Annex , and dat . Currently by default, we ask for an S3 bucket and use s3cmd to sync data in the data folder with the server.", + "text": "Don't ever edit your raw data, especially not manually, and especially not in Excel. Don't overwrite your raw data. Don't save multiple versions of the raw data. Treat the data (and its format) as immutable. The code you write should move the raw data through a pipeline to your final analysis. You shouldn't have to run all of the steps every time you want to make a new figure (see Analysis is a DAG ), but anyone should be able to reproduce the final products with only the code in src and the data in data/raw . Also, if data is immutable, it doesn't need source control in the same way that code does. Therefore, by default, the data folder is included in the .gitignore file. If you have a small amount of data that rarely changes, you may want to include the data in the repository. Github currently warns if files are over 50MB and rejects files over 100MB. Some other options for storing/syncing large data include AWS S3 with a syncing tool (e.g., s3cmd ), Git Large File Storage , Git Annex , and dat . Currently by default, we ask for an S3 bucket and use s3cmd to sync data in the data folder with the server.", "title": "Data is immutable" }, { - "location": "/#notebooks-are-for-exploration", - "text": "Notebooks such as the Jupyter notebook and other literate programming tools are very effective for exploratory data analysis. However, these tools can be less effective for reproducing an analysis. When we use notebooks in our work, we often subdivide the notebooks folder. For example, notebooks/exploratory contains initial explorations, whereas notebooks/reports is more polished work that can be exported as html to the reports directory. Since notebooks are challenging objects for source control (e.g., diffs of the json are often not human-readable and merging is near impossible), we recommended not collaborating directly with others on Jupyter notebooks. There are two steps we recommend for using notebooks effectively: Follow a naming convention that shows the owner and the order the analysis was done in. We use the format step - ghuser - description .ipynb (e.g., 0.3-bull-visualize-distributions.ipynb ). Refactor the good parts. Don't write code to do the same task in multiple notebooks. If it's a data preprocessing task, put it in the pipeline at src/data/make_dataset.py and load data from data/interim . If it's useful utility code, refactor it to src and import it into notebooks with a cell like the following. If updating the system path is icky to you, we'd recommend making a Python package (there is a cookiecutter for that as well) and installing that as an editable package with pip install -e . # Load the autoreload extension\n%load_ext autoreload\n\n# always reload modules marked with %aimport \n%autoreload 1\n\nimport os\nimport sys\n\n# add the 'src' directory as one where we can import modules\nsrc_dir = os.path.join(os.getcwd(), os.pardir, 'src')\nsys.path.append(src_dir)\n\n# import my method from the source code\n%aimport preprocess.build_features\nfrom preprocess.build_features import remove_invalid_data", - "title": "Notebooks are for exploration" + "location": "/#notebooks-are-for-exploration-and-communication", + "text": "Notebook packages like the Jupyter notebook , Beaker notebook , Zeppelin , and other literate programming tools are very effective for exploratory data analysis. However, these tools can be less effective for reproducing an analysis. When we use notebooks in our work, we often subdivide the notebooks folder. For example, notebooks/exploratory contains initial explorations, whereas notebooks/reports is more polished work that can be exported as html to the reports directory. Since notebooks are challenging objects for source control (e.g., diffs of the json are often not human-readable and merging is near impossible), we recommended not collaborating directly with others on Jupyter notebooks. There are two steps we recommend for using notebooks effectively: Follow a naming convention that shows the owner and the order the analysis was done in. We use the format step - ghuser - description .ipynb (e.g., 0.3-bull-visualize-distributions.ipynb ). Refactor the good parts. Don't write code to do the same task in multiple notebooks. If it's a data preprocessing task, put it in the pipeline at src/data/make_dataset.py and load data from data/interim . If it's useful utility code, refactor it to src and import it into notebooks with a cell like the following. If updating the system path is icky to you, we'd recommend making a Python package (there is a cookiecutter for that as well) and installing that as an editable package with pip install -e . # Load the autoreload extension\n%load_ext autoreload\n\n# always reload modules marked with %aimport \n%autoreload 1\n\nimport os\nimport sys\n\n# add the 'src' directory as one where we can import modules\nsrc_dir = os.path.join(os.getcwd(), os.pardir, 'src')\nsys.path.append(src_dir)\n\n# import my method from the source code\n%aimport preprocess.build_features\nfrom preprocess.build_features import remove_invalid_data", + "title": "Notebooks are for exploration and communication" }, { "location": "/#analysis-is-a-dag", @@ -75,6 +75,11 @@ "text": "The first step in reproducing an analysis is always reproducing the computational environment it was run in. You need the same tools, the same libraries, and the same versions to make everything play nicely together. One effective approach to this is use virtualenv (we recommend virtualenvwrapper for managing virtualenvs). By listing all of your requirements in the repository (we include a requirements.txt file) you can easily track the packages needed to recreate the analysis. Here is a good workflow: Run mkvirtualenv when creating a new project pip install the packages that your analysis needs Run pip freeze requirements.txt to pin the exact package versions used to recreate the analysis If you find you need to install another package, run pip freeze requirements.txt again and commit the changes to version control. If you have more complex requirements for recreating your environment, consider a virtual machine based approach such as Docker or Vagrant . Both of these tools use text-based formats (Dockerfile and Vagrantfile respectively) you can easily add to source control to describe how to create a virtual machine with the requirements you need.", "title": "Build from the environment up" }, + { + "location": "/#keep-secrets-out-of-version-control", + "text": "You really don't want to leak your AWS secret key or Postgres username and password on Github. Enough said, mostly \u2014 see the Twelve Factor App principles on this point. We generally use a .env file that, thanks to the .gitignore , never makes it into the repository (secrets should be shared via other means with contributors). The .env file defines secrets as environment variables, and is read in automatically by a package like dotenv in Python.", + "title": "Keep secrets out of version control" + }, { "location": "/#contributing", "text": "The Cookiecutter Data Science project is opinionated, but not afraid to be wrong. Best practices change, tools evolve, and lessons are learned. The goal of this project is to make it easier to start, structure, and share an analysis. Pull requests and filing issues is encouraged. We'd love to hear what works for you, and what doesn't. If you use the Cookiecutter Data Science project, link back to this page or give us a holler and let us know !",