Package: coldstart
Coldstart is a package for automatic data collection and feature engineering that should be used by new and seasoned data scientists/engineers interested in accelerating model development.Data collection and feature engineering are among the most tedious and time-consuming steps in the data science workflow. Coldstart aims to solve this problem by encapsulating efficient patterns and abstracting away low-level details associated with dynamic query templating, query optimization, concurrent execution, memory management, data leakage, and pipeline deployment.Coldstart is meant to be a "Goldilocks" solution that sits somewhere between a collection of version-controlled queries and a full-fledged feature store. If you're making batch predictions that do not require ultra-low latency guarantees or if you're not taking full advantage of the warehouse’s available computing resources (i.e., waiting for queries dozens of queries to run one-by-one), then this package might be perfect for you.Coldstart embraces a code that writes code mindset by exposing a powerful class (FeatureFactory) that retrieves data from various user-defined domains by establishing 1:1 or 1:M relationships with peer-reviewed queries that are templated at runtime based on user input and executed concurrently in one or many batches. The output comes in the form of a single wide dataframe that can be held in memory (i.e., pandas) or on disk (i.e., dask) and then fed directly into a feature engineering/modeling pipeline (e.g., sklearn). Row-level observations are identifiable through the use of composite indexes that have two parts to them: an entity component and a temporal component, which satisfy most tabular supervised ML use cases. When it's time to move from development to production, a user can "freeze" the queries that they will be using in their prediction pipeline.The general order of events looks like this:DocumentationDocumentationis hosted on GitHub Pages.InstallationThe latest version can be installed from PyPI:pipinstallcoldstartUsageHere is a basic example of how to useFeatureFactory:fromcoldstartimportFeatureFactory# Instantiate FeatureFactoryff=FeatureFactory()# List available query domainsmy_domains=ff.list_domains(dialect='bigquery',entity_id='team_id'# replace with your entity_id)print(f'Available domains:{my_domains}')# Set database specdb_spec={'dialect':'bigquery','project_id':PROJECT_ID,# replace with your project if using Big Query'schema':'my_schema'# replace with your schema/dataset}# Start engineff.start_engine(db_spec)# Run feature_factoryff.run(leftmost_table='my_schema.my_leftmost_table',# table with columns: team_id and yfeature_table='my_schema.my_feature_table',entity_id='team_id',domains=my_domains,date_range=['2010-01-01','2015-01-01'],)# Return dataframefeatures_df=ff.get_dataframe()# Stop engineff.stop_engine()# View dataframefeatures_df.head()Seethis notebookfor a more thorough example.Note thatmy_schema.my_leftmost_tablemust be a predefined table with at least 2 columns:entity_idandywhere entity_id is corresponds with the tagged queries in the query bank and y corresponds with the dependent variable that you're eventually modeling. Optionally, you can also include amin_dateand amax_datecolumn so that each row is parameterized accordingly (if you do not include dates in your table, thedate_rangeargument will be used for all records). A typicalleftmost_tablewill look like this:entity_idyabc0def1......Or:entity_idymin_datemax_dateabc02020-01-012020-12-31abc12021-01-012021-12-31def02020-01-012020-12-31def12021-01-012021-12-31............Queries in the query bank must adhere to an established pattern. It's this pattern that makes consistent dynamic runtime templating possible. All queries must:Have a unique file nameTip: By beginning the file name with the corresponding entity and domain name, you will easily be able to estasblish feature lineage back to the query because the final table appends the query name to the column name to ensure uniquenessBe tagged withDIALECT,ENTITY, andDOMAINUse the defaultidxcolumn (which is a concatonation ofentity_id+min_date+max_date) in theSELECTin all CTEs/subqueries and the outer-most queryTip: You do not need to carry theentity_id,min_date, ormax_datedown through CTEs/subqueries because it is baked intoidxUse the{LEFTMOST_TABLE}variable as the left-most table in theFROMUsemin_dateandmax_dateto constrain relevant date columns in theWHEREif dates are involvedHave an outer-most query that usesidxin theGROUP BYif aggregation is involvedA typical query (e.g.,teamGameStats.sql) will look something like this:-- DIALECT: your_dialect (e.g., bigquery)-- ENTITY: your_entity_id (e.g., team_id)-- DOMAIN: your_domain (e.g., game)WITHT1AS(SELECTLMT.idx,A.some_column,B.some_other_column,...FROM{LEFTMOST_TABLE}ASLMTINNERJOINsome_schema.some_table_1ASAONLMT.team_id=A.team_idLEFTJOINsome_schema.some_table_2ASBONA.id=B.team_id...WHEREsome_date_column>=LMT.min_dateANDsome_date_column<=LMT.max_date...)SELECTT1.idx,SUM(T1.some_column)ASsome_sum,SUM(T1.some_other_column)ASsome_other_sum,...FROMT1GROUPBYT1.idxIf you're looking for more fine-grained control over which queries to run, you can use thequeriesparameter, as opposed to thedomainsparameter. Before doing so though, you'll need to familiarize yourself with the queries in the query bank.After running, you should get back a table/dataframe that is as wide as the total number of columns returned in all underlying queries' outer-most SELECT (plusidxandy). Building off of the earlier example, thefeature_tableand/or returned dataframe would look like this:idxyteamGameStats_some_sumteamGameStats_some_other_sum...abc_2010-01-01_2015-01-01050100...def_2010-01-01_2015-01-01025200..................Which can then be passed into a boilerplate pipeline like this:fromsklearn.pipelineimportPipelinefromsklearn.composeimportColumnTransformer,make_column_selectorfromsklearn.ensembleimportRandomForestClassifierfromsklearn.imputeimportSimpleImputerfromsklearn.preprocessingimportOneHotEncoder,StandardScalerfromsklearn.model_selectionimporttrain_test_split# Construct pipelinenumeric_transformer=Pipeline(steps=[('imputer',SimpleImputer(strategy='constant',fill_value=0,copy=False)),('scaler',StandardScaler()),])categorical_transformer=Pipeline(steps=[('imputer',SimpleImputer(strategy='constant',fill_value='NA',copy=False)),('encoder',OneHotEncoder(sparse=False,handle_unknown='ignore')),])preprocessor=ColumnTransformer(transformers=[('num',numeric_transformer,make_column_selector(dtype_include=np.number)),('cat',categorical_transformer,make_column_selector(dtype_include=pd.CategoricalDtype)),])pipe=Pipeline(steps=[('preprocessor',preprocessor),('classifier',RandomForestClassifier()),])# Set features and class labelX=features_df.iloc[:,1:]y=features_df.iloc[:,0]# Train test splitX_train,X_test,y_train,y_test=train_test_split(X,y)# Fit pipelinepipe.fit(X_train,y_train)Again, seethis notebookfor a more thorough example.Features Under DevelopmentSwitch for writing intermediate results to Parquet filesOption to return Dask DataFrameTesting for more databasesRetrying decorator for run_queryContributor GuideBefore contributing to this CVS Health sponsored project, you will need to sign the associatedContributor License Agreement.Seecontributingpage.