diff --git a/.DS_Store b/.DS_Store index cf3a889..1c362cb 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/README.md b/README.md index cc17ed1..999722d 100644 --- a/README.md +++ b/README.md @@ -1,113 +1,31 @@ -# A simple and stupid dashboard for e-mission +# Efforts towards predicting the replaced mode without user labels -Issues: Since this repository is part of a larger project, all issues are tracked in the central docs repository. If you have a question, as suggested by the open source guide, please file an issue instead of sending an email. Since issues are public, other contributors can try to answer the question and benefit from the answer. +## Prerequisites: +- These experiments were conducted on top of the `emission` anaconda environment. Please ensure that this environment is available to you before re-running the code. +- In addition, the script uses `seaborn` for plotting and `pandarallel` for parallel pandas processing. +- Ensure you have the following data sources loaded in your MongoDB Docker container: + - Stage_database (All CEO) + - Durham + - Masscec + - Ride2own + - UPRM NICR +- Once these data sources are procured and loaded in your Mongo container, you will need to add the inferred sections to the data. To do this, please run the [add_sections_and_summaries_to_trips.py](https://github.com/e-mission/e-mission-server/blob/master/bin/historical/migrations/add_sections_and_summaries_to_trips.py) script. **NOTE**: If you see a lot of errors in the log, try to re-run the script by modifying the following line from: -## Development - -We use docker images for the software dependencies since we will not be modifying them here. - -So the steps are: - -#### Launch dev environment - -``` -$ docker-compose -f docker-compose.dev.yml up -Creating network "em-public-dashboard_emission" with the default driver -Creating em-public-dashboard_db_1 ... done -Creating em-public-dashboard_plot-gen_1 ... done -Creating em-public-dashboard_dashboard_1 ... done -... -dashboard_1 | Starting up http-server, serving ./ -dashboard_1 | Available on: -dashboard_1 | http://127.0.0.1:8080 -dashboard_1 | http://172.25.0.3:8080 -dashboard_1 | Hit CTRL-C to stop the server -... -notebook-server_1 | -notebook-server_1 | To access the notebook, open this file in a browser: -notebook-server_1 | file:///root/.local/share/jupyter/runtime/nbserver-22-open.html -notebook-server_1 | Or copy and paste one of these URLs: -notebook-server_1 | http://f8317197efaf:8888/?token=5cfd541b7461a47310c9c8aaa4114f921457a6f17b8ca159 -notebook-server_1 | or http://127.0.0.1:8888/?token=5cfd541b7461a47310c9c8aaa4114f921457a6f17b8ca159 -... -``` - -#### Test the frontend install - -Go to http://localhost:3274/ to see the front-end. Note that the port is *3274* -instead of the *8080* in the logs, since we remap it as part of the docker-compose. - -#### Test the notebook install - -Use the notebook URL from the console but change `8888` to `47962` - -``` -http://127.0.0.1:8888/?token= -``` - -becomes - -``` -http://127.0.0.1:47962/?token= -``` - -#### Load some data - -https://github.com/e-mission/e-mission-server/#quick-start - -There are multiple sources listed there, or you can use the mongodump from: -https://github.com/asiripanich/emdash#loading-test-data - -#### Loading data from a mongodump - -We have a helper script to load data directly from a mongodump. +```language=python +# Before +eps.dispatch(split_lists, skip_if_no_new_data=False, target_fn=add_sections_to_trips) +# After +eps.dispatch(split_lists, skip_if_no_new_data=False, target_fn=None) ``` -$ bash viz_scripts/docker/load_mongodump.sh -``` - -Note that this expects a standard setup with: -- this repository checked out under the `em-public-dashboard` directory, which makes the database name `em-public-dashboard_db_1` -- the incoming mongodump is in tar gz format. This should be true of all canbikeco dumps, you may need to change the `tar xvf` to `unzip` otherwise. The mongo container typically doesn't have zip installed, so using tar is more portable. - -**If you have a non-standard setup, please use your expertise to change the script appropriately.** - -#### Happy visualizations! - -Look at the existing notebooks for examples on how to start. -In particular, before you check in, please make sure that you are reading -inputs correctly, because otherwise, no metrics will be generated. - -### Design decisions - -Dashboards! They are fairly essential for user acceptance, but there are many options to build them. -And the choice of the technology stack for them is particularly fraught. -And for community projects, especially outside computer stack, choosing a technology stack ensures that half your collaborators cannot access it. -For example, choosing python will cause R users to balk and vice versa. -And there is although contributors can write some javascript, picking a charting library again steepens the learning curve. - -So we are going to use a simple and stupid dashboard. -This will consist of a reactive grid layout -(e.g. https://strml.github.io/react-grid-layout/examples/15-drag-from-outside.html) -served by a simple static express server following the instructions at -https://www.thoughts-in-motion.com/articles/creating-a-static-web-server-with-node-js-and-express/ - -The grid layout will display static, pre-generated images using whatever program the user wishes. -The program should take the time range as input and generate a static image shared with the express server. -We have included python examples using ipython notebook and simple python scripts for the following metrics: - -- mode share (notebook) -- purpose share (notebook) -- total number of trips per day (python) -In order to get the prototype out, there are a lot of shortcuts. We can revisit -this later if there is sufficient interest/funding. +This will trigger the intake pipeline for the current db and add the inferred section. -- Using gridster (https://github.com/dsmorse/gridster.js/) and bootstrap instead of react -- Using the pre-built (https://hub.docker.com/r/danjellz/http-server) instead of express -- Using a mounted volume instead of building a custom docker image to make deployment easier -- Using the e-mission server codebase to generate graphs instead of a REST API +- Note 2: The script above did not work for the All CEO data for me. Therefore, I obtained the section durations using the `get_section_durations` method I've written in `scaffolding.py` (you do not have to call this method, it is already handled in the notebooks). Please note that running this script takes a long time and it is advised to cache the generated output. -The one part where we are NOT cutting corners is in the parts where we expect -contributions from others. We are going to build in automated tests for that -part to ensure non-bitrotted code. +## Running the experiments +The order in which the experiments are to be run are denoted by the preceding number. The following is a brief summary about each notebook: +1. `01_extract_db_data.ipynb`: This notebook extracts the data, performs the necessary preprocessing, updates availability indicators, computes cost estimates, and stores the preprocessed data in `data/filtered_trips`. +2. `02_run_trip_level_models.py`: This script reads all the preprocessed data, fits trip-level models with different stratitifications, generates the outputs, and stores them in `outputs/benchmark_results/`. +3. `03_user_level_models.ipynb`: This notebook explores user fingerprints, similarity searching, and naive user-level models. +4. `04_FeatureClustering.ipynb`: This notebook performs two functions: (a) Cluster users based on demographics/trip feature summaries and check for target distributions across clusters, and (b) Cluster users by grouping w.r.t. the target and checking for feature homogeneity within clusters diff --git a/data/README.md b/data/README.md new file mode 100644 index 0000000..222add2 --- /dev/null +++ b/data/README.md @@ -0,0 +1 @@ +Placeholder folder to store data \ No newline at end of file diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml deleted file mode 100644 index b04d63e..0000000 --- a/docker-compose.dev.yml +++ /dev/null @@ -1,47 +0,0 @@ -version: "3" -services: - dashboard: - image: danjellz/http-server:1.4 - depends_on: - - db - ports: - # DASH in numbers - - "3274:8080" - volumes: - - ./frontend:/public - - ./plots:/public/plots - networks: - - emission - notebook-server: - image: em-pub-dash-dev/viz-scripts - build: - context: viz_scripts - dockerfile: docker/Dockerfile.dev - depends_on: - - db - environment: - - DB_HOST=db - - WEB_SERVER_HOST=0.0.0.0 - - CRON_MODE= - - STUDY_CONFIG=stage-program - ports: - # ipynb in numbers - - "47962:8888" - networks: - - emission - volumes: - - ./viz_scripts:/usr/src/app/saved-notebooks - - ./plots:/plots - db: - image: mongo:4.4.0 - volumes: - - mongo-data:/data/db - networks: - - emission - -networks: - emission: - -volumes: - mongo-data: - diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 73a9875..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,44 +0,0 @@ -version: "3" -services: - dashboard: - image: em-pub-dash-prod/frontend - build: frontend - depends_on: - - db - ports: - # DASH in numbers - - "3274:6060" - volumes: - - ./plots:/public/plots - networks: - - emission - notebook-server: - image: em-pub-dash-prod/viz-scripts - build: viz_scripts - depends_on: - - db - environment: - - DB_HOST=db - - WEB_SERVER_HOST=0.0.0.0 - - CRON_MODE=TRUE - - STUDY_CONFIG=stage-program - ports: - # ipynb in numbers - - "47962:8888" - networks: - - emission - volumes: - - ./plots:/plots - db: - image: mongo:4.4.0 - volumes: - - mongo-data:/data/db - networks: - - emission - -networks: - emission: - -volumes: - mongo-data: - diff --git a/frontend/Dockerfile b/frontend/Dockerfile deleted file mode 100644 index 69321ca..0000000 --- a/frontend/Dockerfile +++ /dev/null @@ -1,14 +0,0 @@ -FROM node:18-alpine -VOLUME /public/plots - -RUN mkdir -p /public -WORKDIR /public - -RUN npm install -g npm@8.14.0 && npm i -g http-server - -COPY client /public/client -COPY *.html /public/ - -EXPOSE 6060 -CMD ["http-server", "-p", "6060"] - diff --git a/frontend/client/css/bootstrap.min.css b/frontend/client/css/bootstrap.min.css deleted file mode 100644 index 286cde4..0000000 --- a/frontend/client/css/bootstrap.min.css +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * Bootstrap v4.5.3 (https://getbootstrap.com/) - * Copyright 2011-2020 The Bootstrap Authors - * Copyright 2011-2020 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - */:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#007bff;--secondary:#6c757d;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([class]){color:inherit;text-decoration:none}a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit;text-align:-webkit-match-parent}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#fcf8e3}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote-footer{display:block;font-size:80%;color:#6c757d}.blockquote-footer::before{content:"\2014\00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:90%;color:#6c757d}code{font-size:87.5%;color:#e83e8c;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}.row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{position:relative;width:100%;padding-right:15px;padding-left:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-first{-ms-flex-order:-1;order:-1}.order-last{-ms-flex-order:13;order:13}.order-0{-ms-flex-order:0;order:0}.order-1{-ms-flex-order:1;order:1}.order-2{-ms-flex-order:2;order:2}.order-3{-ms-flex-order:3;order:3}.order-4{-ms-flex-order:4;order:4}.order-5{-ms-flex-order:5;order:5}.order-6{-ms-flex-order:6;order:6}.order-7{-ms-flex-order:7;order:7}.order-8{-ms-flex-order:8;order:8}.order-9{-ms-flex-order:9;order:9}.order-10{-ms-flex-order:10;order:10}.order-11{-ms-flex-order:11;order:11}.order-12{-ms-flex-order:12;order:12}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}@media (min-width:576px){.col-sm{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-sm-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-sm-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-sm-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-sm-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-sm-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-sm-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-sm-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-sm-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-sm-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-sm-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-sm-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-sm-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-sm-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-sm-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-sm-first{-ms-flex-order:-1;order:-1}.order-sm-last{-ms-flex-order:13;order:13}.order-sm-0{-ms-flex-order:0;order:0}.order-sm-1{-ms-flex-order:1;order:1}.order-sm-2{-ms-flex-order:2;order:2}.order-sm-3{-ms-flex-order:3;order:3}.order-sm-4{-ms-flex-order:4;order:4}.order-sm-5{-ms-flex-order:5;order:5}.order-sm-6{-ms-flex-order:6;order:6}.order-sm-7{-ms-flex-order:7;order:7}.order-sm-8{-ms-flex-order:8;order:8}.order-sm-9{-ms-flex-order:9;order:9}.order-sm-10{-ms-flex-order:10;order:10}.order-sm-11{-ms-flex-order:11;order:11}.order-sm-12{-ms-flex-order:12;order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}}@media (min-width:768px){.col-md{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-md-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-md-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-md-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-md-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-md-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-md-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-md-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-md-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-md-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-md-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-md-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-md-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-md-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-md-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-md-first{-ms-flex-order:-1;order:-1}.order-md-last{-ms-flex-order:13;order:13}.order-md-0{-ms-flex-order:0;order:0}.order-md-1{-ms-flex-order:1;order:1}.order-md-2{-ms-flex-order:2;order:2}.order-md-3{-ms-flex-order:3;order:3}.order-md-4{-ms-flex-order:4;order:4}.order-md-5{-ms-flex-order:5;order:5}.order-md-6{-ms-flex-order:6;order:6}.order-md-7{-ms-flex-order:7;order:7}.order-md-8{-ms-flex-order:8;order:8}.order-md-9{-ms-flex-order:9;order:9}.order-md-10{-ms-flex-order:10;order:10}.order-md-11{-ms-flex-order:11;order:11}.order-md-12{-ms-flex-order:12;order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}}@media (min-width:992px){.col-lg{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-lg-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-lg-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-lg-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-lg-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-lg-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-lg-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-lg-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-lg-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-lg-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-lg-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-lg-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-lg-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-lg-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-lg-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-lg-first{-ms-flex-order:-1;order:-1}.order-lg-last{-ms-flex-order:13;order:13}.order-lg-0{-ms-flex-order:0;order:0}.order-lg-1{-ms-flex-order:1;order:1}.order-lg-2{-ms-flex-order:2;order:2}.order-lg-3{-ms-flex-order:3;order:3}.order-lg-4{-ms-flex-order:4;order:4}.order-lg-5{-ms-flex-order:5;order:5}.order-lg-6{-ms-flex-order:6;order:6}.order-lg-7{-ms-flex-order:7;order:7}.order-lg-8{-ms-flex-order:8;order:8}.order-lg-9{-ms-flex-order:9;order:9}.order-lg-10{-ms-flex-order:10;order:10}.order-lg-11{-ms-flex-order:11;order:11}.order-lg-12{-ms-flex-order:12;order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}}@media (min-width:1200px){.col-xl{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-xl-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-xl-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-xl-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-xl-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-xl-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-xl-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-xl-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-xl-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-xl-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-xl-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-xl-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-xl-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-xl-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-xl-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-xl-first{-ms-flex-order:-1;order:-1}.order-xl-last{-ms-flex-order:13;order:13}.order-xl-0{-ms-flex-order:0;order:0}.order-xl-1{-ms-flex-order:1;order:1}.order-xl-2{-ms-flex-order:2;order:2}.order-xl-3{-ms-flex-order:3;order:3}.order-xl-4{-ms-flex-order:4;order:4}.order-xl-5{-ms-flex-order:5;order:5}.order-xl-6{-ms-flex-order:6;order:6}.order-xl-7{-ms-flex-order:7;order:7}.order-xl-8{-ms-flex-order:8;order:8}.order-xl-9{-ms-flex-order:9;order:9}.order-xl-10{-ms-flex-order:10;order:10}.order-xl-11{-ms-flex-order:11;order:11}.order-xl-12{-ms-flex-order:12;order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}}.table{width:100%;margin-bottom:1rem;color:#212529}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #dee2e6}.table-bordered td,.table-bordered th{border:1px solid #dee2e6}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{color:#212529;background-color:rgba(0,0,0,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#b8daff}.table-primary tbody+tbody,.table-primary td,.table-primary th,.table-primary thead th{border-color:#7abaff}.table-hover .table-primary:hover{background-color:#9fcdff}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#9fcdff}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#d6d8db}.table-secondary tbody+tbody,.table-secondary td,.table-secondary th,.table-secondary thead th{border-color:#b3b7bb}.table-hover .table-secondary:hover{background-color:#c8cbcf}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#c8cbcf}.table-success,.table-success>td,.table-success>th{background-color:#c3e6cb}.table-success tbody+tbody,.table-success td,.table-success th,.table-success thead th{border-color:#8fd19e}.table-hover .table-success:hover{background-color:#b1dfbb}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#b1dfbb}.table-info,.table-info>td,.table-info>th{background-color:#bee5eb}.table-info tbody+tbody,.table-info td,.table-info th,.table-info thead th{border-color:#86cfda}.table-hover .table-info:hover{background-color:#abdde5}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#abdde5}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffeeba}.table-warning tbody+tbody,.table-warning td,.table-warning th,.table-warning thead th{border-color:#ffdf7e}.table-hover .table-warning:hover{background-color:#ffe8a1}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffe8a1}.table-danger,.table-danger>td,.table-danger>th{background-color:#f5c6cb}.table-danger tbody+tbody,.table-danger td,.table-danger th,.table-danger thead th{border-color:#ed969e}.table-hover .table-danger:hover{background-color:#f1b0b7}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f1b0b7}.table-light,.table-light>td,.table-light>th{background-color:#fdfdfe}.table-light tbody+tbody,.table-light td,.table-light th,.table-light thead th{border-color:#fbfcfc}.table-hover .table-light:hover{background-color:#ececf6}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>td,.table-dark>th{background-color:#c6c8ca}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#95999c}.table-hover .table-dark:hover{background-color:#b9bbbe}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>td,.table-active>th{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,.075)}.table .thead-dark th{color:#fff;background-color:#343a40;border-color:#454d55}.table .thead-light th{color:#495057;background-color:#e9ecef;border-color:#dee2e6}.table-dark{color:#fff;background-color:#343a40}.table-dark td,.table-dark th,.table-dark thead th{border-color:#454d55}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,.05)}.table-dark.table-hover tbody tr:hover{color:#fff;background-color:rgba(255,255,255,.075)}@media (max-width:575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-md>.table-bordered{border:0}}@media (max-width:991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}.form-control:focus{color:#495057;background-color:#fff;border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.form-control::-webkit-input-placeholder{color:#6c757d;opacity:1}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control:-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}input[type=date].form-control,input[type=datetime-local].form-control,input[type=month].form-control,input[type=time].form-control{-webkit-appearance:none;-moz-appearance:none;appearance:none}select.form-control:focus::-ms-value{color:#495057;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;font-size:1rem;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.form-control-lg{height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}select.form-control[multiple],select.form-control[size]{height:auto}textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#28a745}.valid-tooltip{position:absolute;top:100%;left:0;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(40,167,69,.9);border-radius:.25rem}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#28a745;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-valid,.was-validated .custom-select:valid{border-color:#28a745;padding-right:calc(.75em + 2.3125rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-valid:focus,.was-validated .custom-select:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#28a745}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#28a745}.custom-control-input.is-valid~.custom-control-label::before,.was-validated .custom-control-input:valid~.custom-control-label::before{border-color:#28a745}.custom-control-input.is-valid:checked~.custom-control-label::before,.was-validated .custom-control-input:valid:checked~.custom-control-label::before{border-color:#34ce57;background-color:#34ce57}.custom-control-input.is-valid:focus~.custom-control-label::before,.was-validated .custom-control-input:valid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-control-input.is-valid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:valid:focus:not(:checked)~.custom-control-label::before{border-color:#28a745}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#28a745}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;left:0;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-invalid,.was-validated .custom-select:invalid{border-color:#dc3545;padding-right:calc(.75em + 2.3125rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-invalid:focus,.was-validated .custom-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#dc3545}.custom-control-input.is-invalid~.custom-control-label::before,.was-validated .custom-control-input:invalid~.custom-control-label::before{border-color:#dc3545}.custom-control-input.is-invalid:checked~.custom-control-label::before,.was-validated .custom-control-input:invalid:checked~.custom-control-label::before{border-color:#e4606d;background-color:#e4606d}.custom-control-input.is-invalid:focus~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-control-input.is-invalid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus:not(:checked)~.custom-control-label::before{border-color:#dc3545}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#dc3545}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-inline{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center}.form-inline .form-check{width:100%}@media (min-width:576px){.form-inline label{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .custom-select,.form-inline .input-group{width:auto}.form-inline .form-check{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;-ms-flex-negative:0;flex-shrink:0;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;color:#212529;text-align:center;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529;text-decoration:none}.btn.focus,.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.btn.disabled,.btn:disabled{opacity:.65}.btn:not(:disabled):not(.disabled){cursor:pointer}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:hover{color:#fff;background-color:#0069d9;border-color:#0062cc}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#0069d9;border-color:#0062cc;box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0062cc;border-color:#005cbf}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5a6268;border-color:#545b62}.btn-secondary.focus,.btn-secondary:focus{color:#fff;background-color:#5a6268;border-color:#545b62;box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#545b62;border-color:#4e555b}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:hover{color:#fff;background-color:#218838;border-color:#1e7e34}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#218838;border-color:#1e7e34;box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#1e7e34;border-color:#1c7430}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:hover{color:#fff;background-color:#138496;border-color:#117a8b}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#138496;border-color:#117a8b;box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#117a8b;border-color:#10707f}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#212529;background-color:#e0a800;border-color:#d39e00}.btn-warning.focus,.btn-warning:focus{color:#212529;background-color:#e0a800;border-color:#d39e00;box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#212529;background-color:#d39e00;border-color:#c69500}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#c82333;border-color:#bd2130}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c82333;border-color:#bd2130;box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#bd2130;border-color:#b21f2d}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#212529;background-color:#e2e6ea;border-color:#dae0e5}.btn-light.focus,.btn-light:focus{color:#212529;background-color:#e2e6ea;border-color:#dae0e5;box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-light.disabled,.btn-light:disabled{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#212529;background-color:#dae0e5;border-color:#d3d9df}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark.focus,.btn-dark:focus{color:#fff;background-color:#23272b;border-color:#1d2124;box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-outline-primary{color:#007bff;border-color:#007bff}.btn-outline-primary:hover{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#007bff;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-success{color:#28a745;border-color:#28a745}.btn-outline-success:hover{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#28a745;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-info{color:#17a2b8;border-color:#17a2b8}.btn-outline-info:hover{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#17a2b8;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-dark{color:#343a40;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-link{font-weight:400;color:#007bff;text-decoration:none}.btn-link:hover{color:#0056b3;text-decoration:underline}.btn-link.focus,.btn-link:focus{text-decoration:underline}.btn-link.disabled,.btn-link:disabled{color:#6c757d;pointer-events:none}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropdown,.dropleft,.dropright,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu-left{right:auto;left:0}.dropdown-menu-right{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-left{right:auto;left:0}.dropdown-menu-sm-right{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-left{right:auto;left:0}.dropdown-menu-md-right{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-left{right:auto;left:0}.dropdown-menu-lg-right{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-left{right:auto;left:0}.dropdown-menu-xl-right{right:0;left:auto}}.dropup .dropdown-menu{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=top]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#16181b;text-decoration:none;background-color:#f8f9fa}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#007bff}.dropdown-item.disabled,.dropdown-item:disabled{color:#6c757d;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1.5rem;color:#212529}.btn-group,.btn-group-vertical{position:relative;display:-ms-inline-flexbox;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;-ms-flex:1 1 auto;flex:1 1 auto}.btn-group-vertical>.btn:hover,.btn-group>.btn:hover{z-index:1}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus{z-index:1}.btn-toolbar{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:start;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropright .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropleft .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{-ms-flex-direction:column;flex-direction:column;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:center;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:stretch;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control,.input-group>.form-control-plaintext{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;width:1%;min-width:0;margin-bottom:0}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control,.input-group>.form-control-plaintext+.custom-file,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.form-control{margin-left:-1px}.input-group>.custom-file .custom-file-input:focus~.custom-file-label,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.custom-select:not(:last-child),.input-group>.form-control:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label::after{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-append,.input-group-prepend{display:-ms-flexbox;display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn:focus,.input-group-prepend .btn:focus{z-index:3}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group-lg>.custom-select,.input-group-lg>.form-control:not(textarea){height:calc(1.5em + 1rem + 2px)}.input-group-lg>.custom-select,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.input-group-sm>.custom-select,.input-group-sm>.form-control:not(textarea){height:calc(1.5em + .5rem + 2px)}.input-group-sm>.custom-select,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1.75rem}.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-append:not(:last-child)>.btn,.input-group>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;z-index:1;display:block;min-height:1.5rem;padding-left:1.5rem;-webkit-print-color-adjust:exact;color-adjust:exact}.custom-control-inline{display:-ms-inline-flexbox;display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;left:0;z-index:-1;width:1rem;height:1.25rem;opacity:0}.custom-control-input:checked~.custom-control-label::before{color:#fff;border-color:#007bff;background-color:#007bff}.custom-control-input:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-control-input:focus:not(:checked)~.custom-control-label::before{border-color:#80bdff}.custom-control-input:not(:disabled):active~.custom-control-label::before{color:#fff;background-color:#b3d7ff;border-color:#b3d7ff}.custom-control-input:disabled~.custom-control-label,.custom-control-input[disabled]~.custom-control-label{color:#6c757d}.custom-control-input:disabled~.custom-control-label::before,.custom-control-input[disabled]~.custom-control-label::before{background-color:#e9ecef}.custom-control-label{position:relative;margin-bottom:0;vertical-align:top}.custom-control-label::before{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;pointer-events:none;content:"";background-color:#fff;border:#adb5bd solid 1px}.custom-control-label::after{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;content:"";background:no-repeat 50%/50% 50%}.custom-checkbox .custom-control-label::before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::before{border-color:#007bff;background-color:#007bff}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-switch{padding-left:2.25rem}.custom-switch .custom-control-label::before{left:-2.25rem;width:1.75rem;pointer-events:all;border-radius:.5rem}.custom-switch .custom-control-label::after{top:calc(.25rem + 2px);left:calc(-2.25rem + 2px);width:calc(1rem - 4px);height:calc(1rem - 4px);background-color:#adb5bd;border-radius:.5rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-switch .custom-control-label::after{transition:none}}.custom-switch .custom-control-input:checked~.custom-control-label::after{background-color:#fff;-webkit-transform:translateX(.75rem);transform:translateX(.75rem)}.custom-switch .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-select{display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem 1.75rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;vertical-align:middle;background:#fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px;border:1px solid #ced4da;border-radius:.25rem;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-select:focus{border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-select:focus::-ms-value{color:#495057;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{display:none}.custom-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}.custom-select-sm{height:calc(1.5em + .5rem + 2px);padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.custom-select-lg{height:calc(1.5em + 1rem + 2px);padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.custom-file{position:relative;display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(1.5em + .75rem + 2px);margin:0;opacity:0}.custom-file-input:focus~.custom-file-label{border-color:#80bdff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-file-input:disabled~.custom-file-label,.custom-file-input[disabled]~.custom-file-label{background-color:#e9ecef}.custom-file-input:lang(en)~.custom-file-label::after{content:"Browse"}.custom-file-input~.custom-file-label[data-browse]::after{content:attr(data-browse)}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:calc(1.5em + .75rem);padding:.375rem .75rem;line-height:1.5;color:#495057;content:"Browse";background-color:#e9ecef;border-left:inherit;border-radius:0 .25rem .25rem 0}.custom-range{width:100%;height:1.4rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-range:focus{outline:0}.custom-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#007bff;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#b3d7ff}.custom-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#007bff;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-moz-range-thumb{-moz-transition:none;transition:none}}.custom-range::-moz-range-thumb:active{background-color:#b3d7ff}.custom-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-ms-thumb{width:1rem;height:1rem;margin-top:0;margin-right:.2rem;margin-left:.2rem;background-color:#007bff;border:0;border-radius:1rem;-ms-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-ms-thumb{-ms-transition:none;transition:none}}.custom-range::-ms-thumb:active{background-color:#b3d7ff}.custom-range::-ms-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:transparent;border-color:transparent;border-width:.5rem}.custom-range::-ms-fill-lower{background-color:#dee2e6;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px;background-color:#dee2e6;border-radius:1rem}.custom-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#adb5bd}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#adb5bd}.custom-control-label::before,.custom-file-label,.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-control-label::before,.custom-file-label,.custom-select{transition:none}}.nav{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-item{margin-bottom:-1px}.nav-tabs .nav-link{border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#007bff}.nav-fill .nav-item,.nav-fill>.nav-link{-ms-flex:1 1 auto;flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between;padding:.5rem 1rem}.navbar .container,.navbar .container-fluid,.navbar .container-lg,.navbar .container-md,.navbar .container-sm,.navbar .container-xl{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{-ms-flex-preferred-size:100%;flex-basis:100%;-ms-flex-positive:1;flex-grow:1;-ms-flex-align:center;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:no-repeat center center;background-size:100% 100%}@media (max-width:575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{padding-right:0;padding-left:0}}@media (min-width:576px){.navbar-expand-sm{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-sm .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-sm .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width:767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{padding-right:0;padding-left:0}}@media (min-width:768px){.navbar-expand-md{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-md .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-md .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width:991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{padding-right:0;padding-left:0}}@media (min-width:992px){.navbar-expand-lg{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-lg .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-lg .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width:1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{padding-right:0;padding-left:0}}@media (min-width:1200px){.navbar-expand-xl{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-xl .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-xl .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.5);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.5)}.navbar-light .navbar-text a{color:rgba(0,0,0,.9)}.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.5);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.5)}.navbar-dark .navbar-text a{color:#fff}.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{-ms-flex:1 1 auto;flex:1 1 auto;min-height:1px;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-footer{padding:.75rem 1.25rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.625rem;margin-bottom:-.75rem;margin-left:-.625rem;border-bottom:0}.card-header-pills{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem;border-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom,.card-img-top{-ms-flex-negative:0;flex-shrink:0;width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-deck .card{margin-bottom:15px}@media (min-width:576px){.card-deck{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{-ms-flex:1 0 0%;flex:1 0 0%;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group>.card{margin-bottom:15px}@media (min-width:576px){.card-group{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap}.card-group>.card{-ms-flex:1 0 0%;flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:576px){.card-columns{-webkit-column-count:3;-moz-column-count:3;column-count:3;-webkit-column-gap:1.25rem;-moz-column-gap:1.25rem;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion{overflow-anchor:none}.accordion>.card{overflow:hidden}.accordion>.card:not(:last-of-type){border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion>.card:not(:first-of-type){border-top-left-radius:0;border-top-right-radius:0}.accordion>.card>.card-header{border-radius:0;margin-bottom:-1px}.breadcrumb{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#e9ecef;border-radius:.25rem}.breadcrumb-item{display:-ms-flexbox;display:flex}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{display:inline-block;padding-right:.5rem;color:#6c757d;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{display:-ms-flexbox;display:flex;padding-left:0;list-style:none;border-radius:.25rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#007bff;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{z-index:2;color:#0056b3;text-decoration:none;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.page-item.active .page-link{z-index:3;color:#fff;background-color:#007bff;border-color:#007bff}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.badge{transition:none}}a.badge:focus,a.badge:hover{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#007bff}a.badge-primary:focus,a.badge-primary:hover{color:#fff;background-color:#0062cc}a.badge-primary.focus,a.badge-primary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.badge-secondary{color:#fff;background-color:#6c757d}a.badge-secondary:focus,a.badge-secondary:hover{color:#fff;background-color:#545b62}a.badge-secondary.focus,a.badge-secondary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.badge-success{color:#fff;background-color:#28a745}a.badge-success:focus,a.badge-success:hover{color:#fff;background-color:#1e7e34}a.badge-success.focus,a.badge-success:focus{outline:0;box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.badge-info{color:#fff;background-color:#17a2b8}a.badge-info:focus,a.badge-info:hover{color:#fff;background-color:#117a8b}a.badge-info.focus,a.badge-info:focus{outline:0;box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.badge-warning{color:#212529;background-color:#ffc107}a.badge-warning:focus,a.badge-warning:hover{color:#212529;background-color:#d39e00}a.badge-warning.focus,a.badge-warning:focus{outline:0;box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.badge-danger{color:#fff;background-color:#dc3545}a.badge-danger:focus,a.badge-danger:hover{color:#fff;background-color:#bd2130}a.badge-danger.focus,a.badge-danger:focus{outline:0;box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.badge-light{color:#212529;background-color:#f8f9fa}a.badge-light:focus,a.badge-light:hover{color:#212529;background-color:#dae0e5}a.badge-light.focus,a.badge-light:focus{outline:0;box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.badge-dark{color:#fff;background-color:#343a40}a.badge-dark:focus,a.badge-dark:hover{color:#fff;background-color:#1d2124}a.badge-dark.focus,a.badge-dark:focus{outline:0;box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#e9ecef;border-radius:.3rem}@media (min-width:576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;z-index:2;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#004085;background-color:#cce5ff;border-color:#b8daff}.alert-primary hr{border-top-color:#9fcdff}.alert-primary .alert-link{color:#002752}.alert-secondary{color:#383d41;background-color:#e2e3e5;border-color:#d6d8db}.alert-secondary hr{border-top-color:#c8cbcf}.alert-secondary .alert-link{color:#202326}.alert-success{color:#155724;background-color:#d4edda;border-color:#c3e6cb}.alert-success hr{border-top-color:#b1dfbb}.alert-success .alert-link{color:#0b2e13}.alert-info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.alert-info hr{border-top-color:#abdde5}.alert-info .alert-link{color:#062c33}.alert-warning{color:#856404;background-color:#fff3cd;border-color:#ffeeba}.alert-warning hr{border-top-color:#ffe8a1}.alert-warning .alert-link{color:#533f03}.alert-danger{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}.alert-danger hr{border-top-color:#f1b0b7}.alert-danger .alert-link{color:#491217}.alert-light{color:#818182;background-color:#fefefe;border-color:#fdfdfe}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#686868}.alert-dark{color:#1b1e21;background-color:#d6d8d9;border-color:#c6c8ca}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#040505}@-webkit-keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:-ms-flexbox;display:flex;height:1rem;overflow:hidden;line-height:0;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#007bff;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:progress-bar-stripes 1s linear infinite;animation:progress-bar-stripes 1s linear infinite}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.media{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start}.media-body{-ms-flex:1;flex:1}.list-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#007bff;border-color:#007bff}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:576px){.list-group-horizontal-sm{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:768px){.list-group-horizontal-md{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:992px){.list-group-horizontal-lg{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#004085;background-color:#b8daff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#004085;background-color:#9fcdff}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#004085;border-color:#004085}.list-group-item-secondary{color:#383d41;background-color:#d6d8db}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#383d41;background-color:#c8cbcf}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#383d41;border-color:#383d41}.list-group-item-success{color:#155724;background-color:#c3e6cb}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#155724;background-color:#b1dfbb}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#155724;border-color:#155724}.list-group-item-info{color:#0c5460;background-color:#bee5eb}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#0c5460;background-color:#abdde5}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#0c5460;border-color:#0c5460}.list-group-item-warning{color:#856404;background-color:#ffeeba}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#856404;background-color:#ffe8a1}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#856404;border-color:#856404}.list-group-item-danger{color:#721c24;background-color:#f5c6cb}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#721c24;background-color:#f1b0b7}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#721c24;border-color:#721c24}.list-group-item-light{color:#818182;background-color:#fdfdfe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#818182;background-color:#ececf6}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#818182;border-color:#818182}.list-group-item-dark{color:#1b1e21;background-color:#c6c8ca}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#1b1e21;background-color:#b9bbbe}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#1b1e21;border-color:#1b1e21}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:hover{color:#000;text-decoration:none}.close:not(:disabled):not(.disabled):focus,.close:not(:disabled):not(.disabled):hover{opacity:.75}button.close{padding:0;background-color:transparent;border:0}a.close.disabled{pointer-events:none}.toast{-ms-flex-preferred-size:350px;flex-basis:350px;max-width:350px;font-size:.875rem;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .25rem .75rem rgba(0,0,0,.1);opacity:0;border-radius:.25rem}.toast:not(:last-child){margin-bottom:.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.25rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.toast-body{padding:.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out;-webkit-transform:translate(0,-50px);transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{-webkit-transform:none;transform:none}.modal.modal-static .modal-dialog{-webkit-transform:scale(1.02);transform:scale(1.02)}.modal-dialog-scrollable{display:-ms-flexbox;display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-footer,.modal-dialog-scrollable .modal-header{-ms-flex-negative:0;flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;min-height:calc(100% - 1rem)}.modal-dialog-centered::before{display:block;height:calc(100vh - 1rem);height:-webkit-min-content;height:-moz-min-content;height:min-content;content:""}.modal-dialog-centered.modal-dialog-scrollable{-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;height:100%}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable::before{content:none}.modal-content{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:justify;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .close{padding:1rem 1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem}.modal-footer{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:end;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered::before{height:calc(100vh - 3.5rem);height:-webkit-min-content;height:-moz-min-content;height:min-content}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow::before,.bs-tooltip-top .arrow::before{top:0;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=right] .arrow::before,.bs-tooltip-right .arrow::before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow::before,.bs-tooltip-bottom .arrow::before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=left] .arrow::before,.bs-tooltip-left .arrow::before{left:0;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .arrow{position:absolute;display:block;width:1rem;height:.5rem;margin:0 .3rem}.popover .arrow::after,.popover .arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top]>.arrow,.bs-popover-top>.arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=top]>.arrow::before,.bs-popover-top>.arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=top]>.arrow::after,.bs-popover-top>.arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right]>.arrow,.bs-popover-right>.arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=right]>.arrow::before,.bs-popover-right>.arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=right]>.arrow::after,.bs-popover-right>.arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom]>.arrow,.bs-popover-bottom>.arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=bottom]>.arrow::before,.bs-popover-bottom>.arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=bottom]>.arrow::after,.bs-popover-bottom>.arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[x-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left]>.arrow,.bs-popover-left>.arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=left]>.arrow::before,.bs-popover-left>.arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=left]>.arrow::after,.bs-popover-left>.arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{-ms-touch-action:pan-y;touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:-webkit-transform .6s ease-in-out;transition:transform .6s ease-in-out;transition:transform .6s ease-in-out,-webkit-transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-right,.carousel-item-next:not(.carousel-item-left){-webkit-transform:translateX(100%);transform:translateX(100%)}.active.carousel-item-left,.carousel-item-prev:not(.carousel-item-right){-webkit-transform:translateX(-100%);transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;-webkit-transform:none;transform:none}.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:15%;color:#fff;text-align:center;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:20px;height:20px;background:no-repeat 50%/100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5L4.25 4l2.5-2.5L5.25 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5L3.75 4l-2.5 2.5L2.75 8l4-4-4-4z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:15;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{box-sizing:content-box;-ms-flex:0 1 auto;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators li{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}@-webkit-keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:spinner-border .75s linear infinite;animation:spinner-border .75s linear infinite}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1;-webkit-transform:none;transform:none}}@keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1;-webkit-transform:none;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:spinner-grow .75s linear infinite;animation:spinner-grow .75s linear infinite}.spinner-grow-sm{width:1rem;height:1rem}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#007bff!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#0062cc!important}.bg-secondary{background-color:#6c757d!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#545b62!important}.bg-success{background-color:#28a745!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#1e7e34!important}.bg-info{background-color:#17a2b8!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#117a8b!important}.bg-warning{background-color:#ffc107!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00!important}.bg-danger{background-color:#dc3545!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#bd2130!important}.bg-light{background-color:#f8f9fa!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#dae0e5!important}.bg-dark{background-color:#343a40!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #dee2e6!important}.border-top{border-top:1px solid #dee2e6!important}.border-right{border-right:1px solid #dee2e6!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-left{border-left:1px solid #dee2e6!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#007bff!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#28a745!important}.border-info{border-color:#17a2b8!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#343a40!important}.border-white{border-color:#fff!important}.rounded-sm{border-radius:.2rem!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-right{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-left{border-top-left-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-lg{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-0{border-radius:0!important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:-ms-flexbox!important;display:flex!important}.d-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}@media (min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:-ms-flexbox!important;display:flex!important}.d-sm-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:-ms-flexbox!important;display:flex!important}.d-md-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:-ms-flexbox!important;display:flex!important}.d-lg-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:-ms-flexbox!important;display:flex!important}.d-xl-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:-ms-flexbox!important;display:flex!important}.d-print-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.857143%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-4by3::before{padding-top:75%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-center{-ms-flex-align:center!important;align-items:center!important}.align-items-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}@media (min-width:576px){.flex-sm-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-sm-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-sm-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-sm-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-sm-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-sm-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-sm-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-sm-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-sm-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-sm-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-sm-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-sm-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-sm-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-sm-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-sm-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-sm-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-sm-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-sm-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-sm-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-sm-center{-ms-flex-align:center!important;align-items:center!important}.align-items-sm-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-sm-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-sm-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-sm-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-sm-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-sm-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-sm-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-sm-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-sm-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-sm-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-sm-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-sm-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-sm-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-sm-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:768px){.flex-md-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-md-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-md-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-md-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-md-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-md-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-md-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-md-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-md-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-md-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-md-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-md-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-md-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-md-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-md-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-md-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-md-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-md-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-md-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-md-center{-ms-flex-align:center!important;align-items:center!important}.align-items-md-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-md-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-md-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-md-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-md-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-md-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-md-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-md-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-md-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-md-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-md-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-md-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-md-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-md-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:992px){.flex-lg-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-lg-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-lg-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-lg-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-lg-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-lg-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-lg-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-lg-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-lg-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-lg-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-lg-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-lg-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-lg-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-lg-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-lg-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-lg-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-lg-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-lg-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-lg-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-lg-center{-ms-flex-align:center!important;align-items:center!important}.align-items-lg-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-lg-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-lg-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-lg-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-lg-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-lg-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-lg-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-lg-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-lg-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-lg-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-lg-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-lg-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-lg-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-lg-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-xl-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-xl-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-xl-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-xl-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-xl-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-xl-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-xl-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-xl-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-xl-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-xl-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-xl-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-xl-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-xl-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-xl-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-xl-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-xl-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-xl-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-xl-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-xl-center{-ms-flex-align:center!important;align-items:center!important}.align-items-xl-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-xl-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-xl-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-xl-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-xl-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-xl-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-xl-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-xl-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-xl-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-xl-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-xl-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-xl-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-xl-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-xl-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:576px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:768px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:992px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;-ms-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;-ms-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;-ms-user-select:none!important;user-select:none!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}@supports ((position:-webkit-sticky) or (position:sticky)){.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.min-vw-100{min-width:100vw!important}.min-vh-100{min-height:100vh!important}.vw-100{width:100vw!important}.vh-100{height:100vh!important}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-n1{margin:-.25rem!important}.mt-n1,.my-n1{margin-top:-.25rem!important}.mr-n1,.mx-n1{margin-right:-.25rem!important}.mb-n1,.my-n1{margin-bottom:-.25rem!important}.ml-n1,.mx-n1{margin-left:-.25rem!important}.m-n2{margin:-.5rem!important}.mt-n2,.my-n2{margin-top:-.5rem!important}.mr-n2,.mx-n2{margin-right:-.5rem!important}.mb-n2,.my-n2{margin-bottom:-.5rem!important}.ml-n2,.mx-n2{margin-left:-.5rem!important}.m-n3{margin:-1rem!important}.mt-n3,.my-n3{margin-top:-1rem!important}.mr-n3,.mx-n3{margin-right:-1rem!important}.mb-n3,.my-n3{margin-bottom:-1rem!important}.ml-n3,.mx-n3{margin-left:-1rem!important}.m-n4{margin:-1.5rem!important}.mt-n4,.my-n4{margin-top:-1.5rem!important}.mr-n4,.mx-n4{margin-right:-1.5rem!important}.mb-n4,.my-n4{margin-bottom:-1.5rem!important}.ml-n4,.mx-n4{margin-left:-1.5rem!important}.m-n5{margin:-3rem!important}.mt-n5,.my-n5{margin-top:-3rem!important}.mr-n5,.mx-n5{margin-right:-3rem!important}.mb-n5,.my-n5{margin-bottom:-3rem!important}.ml-n5,.mx-n5{margin-left:-3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:576px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-n1{margin:-.25rem!important}.mt-sm-n1,.my-sm-n1{margin-top:-.25rem!important}.mr-sm-n1,.mx-sm-n1{margin-right:-.25rem!important}.mb-sm-n1,.my-sm-n1{margin-bottom:-.25rem!important}.ml-sm-n1,.mx-sm-n1{margin-left:-.25rem!important}.m-sm-n2{margin:-.5rem!important}.mt-sm-n2,.my-sm-n2{margin-top:-.5rem!important}.mr-sm-n2,.mx-sm-n2{margin-right:-.5rem!important}.mb-sm-n2,.my-sm-n2{margin-bottom:-.5rem!important}.ml-sm-n2,.mx-sm-n2{margin-left:-.5rem!important}.m-sm-n3{margin:-1rem!important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem!important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem!important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem!important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem!important}.m-sm-n4{margin:-1.5rem!important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem!important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem!important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem!important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem!important}.m-sm-n5{margin:-3rem!important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem!important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem!important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem!important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-n1{margin:-.25rem!important}.mt-md-n1,.my-md-n1{margin-top:-.25rem!important}.mr-md-n1,.mx-md-n1{margin-right:-.25rem!important}.mb-md-n1,.my-md-n1{margin-bottom:-.25rem!important}.ml-md-n1,.mx-md-n1{margin-left:-.25rem!important}.m-md-n2{margin:-.5rem!important}.mt-md-n2,.my-md-n2{margin-top:-.5rem!important}.mr-md-n2,.mx-md-n2{margin-right:-.5rem!important}.mb-md-n2,.my-md-n2{margin-bottom:-.5rem!important}.ml-md-n2,.mx-md-n2{margin-left:-.5rem!important}.m-md-n3{margin:-1rem!important}.mt-md-n3,.my-md-n3{margin-top:-1rem!important}.mr-md-n3,.mx-md-n3{margin-right:-1rem!important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem!important}.ml-md-n3,.mx-md-n3{margin-left:-1rem!important}.m-md-n4{margin:-1.5rem!important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem!important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem!important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem!important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem!important}.m-md-n5{margin:-3rem!important}.mt-md-n5,.my-md-n5{margin-top:-3rem!important}.mr-md-n5,.mx-md-n5{margin-right:-3rem!important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem!important}.ml-md-n5,.mx-md-n5{margin-left:-3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:992px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-n1{margin:-.25rem!important}.mt-lg-n1,.my-lg-n1{margin-top:-.25rem!important}.mr-lg-n1,.mx-lg-n1{margin-right:-.25rem!important}.mb-lg-n1,.my-lg-n1{margin-bottom:-.25rem!important}.ml-lg-n1,.mx-lg-n1{margin-left:-.25rem!important}.m-lg-n2{margin:-.5rem!important}.mt-lg-n2,.my-lg-n2{margin-top:-.5rem!important}.mr-lg-n2,.mx-lg-n2{margin-right:-.5rem!important}.mb-lg-n2,.my-lg-n2{margin-bottom:-.5rem!important}.ml-lg-n2,.mx-lg-n2{margin-left:-.5rem!important}.m-lg-n3{margin:-1rem!important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem!important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem!important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem!important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem!important}.m-lg-n4{margin:-1.5rem!important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem!important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem!important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem!important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem!important}.m-lg-n5{margin:-3rem!important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem!important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem!important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem!important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-n1{margin:-.25rem!important}.mt-xl-n1,.my-xl-n1{margin-top:-.25rem!important}.mr-xl-n1,.mx-xl-n1{margin-right:-.25rem!important}.mb-xl-n1,.my-xl-n1{margin-bottom:-.25rem!important}.ml-xl-n1,.mx-xl-n1{margin-left:-.25rem!important}.m-xl-n2{margin:-.5rem!important}.mt-xl-n2,.my-xl-n2{margin-top:-.5rem!important}.mr-xl-n2,.mx-xl-n2{margin-right:-.5rem!important}.mb-xl-n2,.my-xl-n2{margin-bottom:-.5rem!important}.ml-xl-n2,.mx-xl-n2{margin-left:-.5rem!important}.m-xl-n3{margin:-1rem!important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem!important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem!important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem!important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem!important}.m-xl-n4{margin:-1.5rem!important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem!important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem!important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem!important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem!important}.m-xl-n5{margin:-3rem!important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem!important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem!important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem!important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;pointer-events:auto;content:"";background-color:rgba(0,0,0,0)}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace!important}.text-justify{text-align:justify!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:576px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-lighter{font-weight:lighter!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-weight-bolder{font-weight:bolder!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#007bff!important}a.text-primary:focus,a.text-primary:hover{color:#0056b3!important}.text-secondary{color:#6c757d!important}a.text-secondary:focus,a.text-secondary:hover{color:#494f54!important}.text-success{color:#28a745!important}a.text-success:focus,a.text-success:hover{color:#19692c!important}.text-info{color:#17a2b8!important}a.text-info:focus,a.text-info:hover{color:#0f6674!important}.text-warning{color:#ffc107!important}a.text-warning:focus,a.text-warning:hover{color:#ba8b00!important}.text-danger{color:#dc3545!important}a.text-danger:focus,a.text-danger:hover{color:#a71d2a!important}.text-light{color:#f8f9fa!important}a.text-light:focus,a.text-light:hover{color:#cbd3da!important}.text-dark{color:#343a40!important}a.text-dark:focus,a.text-dark:hover{color:#121416!important}.text-body{color:#212529!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:rgba(255,255,255,.5)!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.text-decoration-none{text-decoration:none!important}.text-break{word-break:break-word!important;word-wrap:break-word!important}.text-reset{color:inherit!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,::after,::before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #adb5bd;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px!important}.container{min-width:992px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #dee2e6!important}.table-dark{color:inherit}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#dee2e6}.table .thead-dark th{color:inherit;border-color:#dee2e6}} -/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/frontend/client/css/dashboard.css b/frontend/client/css/dashboard.css deleted file mode 100644 index 45aebbc..0000000 --- a/frontend/client/css/dashboard.css +++ /dev/null @@ -1,99 +0,0 @@ -/*! gridster.js - v0.6.10 - 2015-05-31 -* https://dsmorse.github.io/gridster.js/ -* Copyright (c) 2015 ducksboard; Licensed MIT */ -body { - font-size: 16px; - font-family: 'Helvetica Neue', Arial, sans-serif; - color: #ffffff; - margin: 0; -} -h1, -h2, -h3, -p { - margin: 10px; -} -table { - border-collapse: collapse; - border-spacing: 0; -} -.demo { - margin: 3em 0; - padding: 7.5em 0 5.5em; - background: #26941f; -} -.demo:hover .gridster { - margin: 0 auto; - opacity: .8; - -webkit-transition: opacity .6s; - -moz-transition: opacity .6s; - -o-transition: opacity .6s; - -ms-transition: opacity .6s; - transition: opacity .6s; -} -.content { - color: white; -} -.gridster .gs-w { - background: #61A9CF; - cursor: pointer; - -webkit-box-shadow: 0 0 5px rgba(0, 0, 0, 0.3); - box-shadow: 0 0 5px rgba(0, 0, 0, 0.3); -} -.gridster .player { - -webkit-box-shadow: 3px 3px 5px rgba(0, 0, 0, 0.3); - box-shadow: 3px 3px 5px rgba(0, 0, 0, 0.3); - background: #BBB; -} -.gridster .gs-w.try { - background-image: url(../img/sprite.png); - background-repeat: no-repeat; - background-position: 37px -169px; -} -.gridster .preview-holder { - border: none !important; - border-radius: 0 !important; - background: red !important; -} -.gridster ul { - background-color: #EFEFEF; -} -.gridster li { - font-size: 1em; - font-weight: bold; - text-align: center; - line-height: 100%; -} -ul { - list-style-type: none; -} -li { - list-style: none; - font-weight: bold; -} -.gridster-box { - position: relative; - width: 100%; - height: 100%; -} -.controls { - margin-bottom: 20px; -} -.bg-main-website { - background: url("../client/img/bg-pattern.png"), #7b4397; - background: url("../client/img/bg-pattern.png"), -webkit-linear-gradient(to left, #7b4397, #dc2430); - background: url("../client/img/bg-pattern.png"), linear-gradient(to left, #7b4397, #dc2430); -} -button { - background: #ff515d; - color: white; - border: none; - border-radius: 5px; - margin-left: 5px; -} -button.remove { - background: #fff0; - color: black; - border-color: black; -} -/*# sourceMappingURL=demos/assets/css/demo.css.map */ diff --git a/frontend/client/css/jquery.gridster.min.css b/frontend/client/css/jquery.gridster.min.css deleted file mode 100644 index 01d63b4..0000000 --- a/frontend/client/css/jquery.gridster.min.css +++ /dev/null @@ -1,2 +0,0 @@ -/*! gridster.js - v0.7.0 - 2016-02-26 - * https://dsmorse.github.io/gridster.js/ - Copyright (c) 2016 ducksboard; Licensed MIT */ -.gridster{position:relative}.gridster>*{-webkit-transition:height .4s,width .4s;-moz-transition:height .4s,width .4s;-o-transition:height .4s,width .4s;-ms-transition:height .4s,width .4s;transition:height .4s,width .4s}.gridster .gs-w{z-index:2;position:absolute}.gridster .preview-holder{z-index:1;position:absolute;background-color:#fff;border-color:#fff;opacity:.3}.gridster .player-revert{z-index:10!important;-webkit-transition:left .3s,top .3s!important;-moz-transition:left .3s,top .3s!important;-o-transition:left .3s,top .3s!important;transition:left .3s,top .3s!important}.gridster.collapsed{height:auto!important}.gridster.collapsed .gs-w{position:static!important}.ready .gs-w:not(.preview-holder),.ready .resize-preview-holder{-webkit-transition:opacity .3s,left .3s,top .3s,width .3s,height .3s;-moz-transition:opacity .3s,left .3s,top .3s,width .3s,height .3s;-o-transition:opacity .3s,left .3s,top .3s,width .3s,height .3s;transition:opacity .3s,left .3s,top .3s,width .3s,height .3s}.gridster .dragging,.gridster .resizing{z-index:10!important;-webkit-transition:all 0s!important;-moz-transition:all 0s!important;-o-transition:all 0s!important;transition:all 0s!important}.gs-resize-handle{position:absolute;z-index:1}.gs-resize-handle-both{width:20px;height:20px;bottom:-8px;right:-8px;background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pg08IS0tIEdlbmVyYXRvcjogQWRvYmUgRmlyZXdvcmtzIENTNiwgRXhwb3J0IFNWRyBFeHRlbnNpb24gYnkgQWFyb24gQmVhbGwgKGh0dHA6Ly9maXJld29ya3MuYWJlYWxsLmNvbSkgLiBWZXJzaW9uOiAwLjYuMSAgLS0+DTwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DTxzdmcgaWQ9IlVudGl0bGVkLVBhZ2UlMjAxIiB2aWV3Qm94PSIwIDAgNiA2IiBzdHlsZT0iYmFja2dyb3VuZC1jb2xvcjojZmZmZmZmMDAiIHZlcnNpb249IjEuMSINCXhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbDpzcGFjZT0icHJlc2VydmUiDQl4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjZweCIgaGVpZ2h0PSI2cHgiDT4NCTxnIG9wYWNpdHk9IjAuMzAyIj4NCQk8cGF0aCBkPSJNIDYgNiBMIDAgNiBMIDAgNC4yIEwgNCA0LjIgTCA0LjIgNC4yIEwgNC4yIDAgTCA2IDAgTCA2IDYgTCA2IDYgWiIgZmlsbD0iIzAwMDAwMCIvPg0JPC9nPg08L3N2Zz4=);background-position:top left;background-repeat:no-repeat;cursor:se-resize;z-index:20}.gs-resize-handle-x{top:0;bottom:13px;right:-5px;width:10px;cursor:e-resize}.gs-resize-handle-y{left:0;right:13px;bottom:-5px;height:10px;cursor:s-resize}.gs-w:hover .gs-resize-handle,.resizing .gs-resize-handle{opacity:1}.gs-resize-handle,.gs-w.dragging .gs-resize-handle{opacity:0}.gs-resize-disabled .gs-resize-handle,[data-max-sizex="1"] .gs-resize-handle-x,[data-max-sizey="1"] .gs-resize-handle-y,[data-max-sizey="1"][data-max-sizex="1"] .gs-resize-handle{display:none!important} \ No newline at end of file diff --git a/frontend/client/css/nrel.application.css b/frontend/client/css/nrel.application.css deleted file mode 100755 index 4e8baa0..0000000 --- a/frontend/client/css/nrel.application.css +++ /dev/null @@ -1,114 +0,0 @@ -body, html { overflow-x:hidden; } -#content { - margin-top:.5em -} - -header .logo{ - background-position:center center -} -@media (min-width:768px){ - #content { - margin-top:1em - } - header .logo{ - background-position:center right - } - .navbar-wrapper .container.app{ - border-top:none - } -} -header a.app-name,header a.app-name:hover,header a.app-name:visited{ - color:#4B545A; - display:block; - font-family:Roboto; - font-size:1.5rem; - line-height:1.2em; - margin:0 0 .3em; - text-align:center; - text-decoration:none -} -@media (min-width:768px){ - header a.app-name,header a.app-name:hover,header a.app-name:visited { - font-size:1.85rem; - margin-top:28px; - text-align:left - } -} -/* Interior page header */ -header .logo.interior { - height: 65px; - background-size: 137px; -} -#interior.program-header .navbar-light .navbar-nav > li > a {padding: 5px 15px;} -@media (min-width: 768px) { - .banner-logo { - min-height: 75px; - } -} -/* Search */ -.site-searchbar-toggle { - border: 1px solid #5e6a71; - border-radius: 5px; - color: #5e6a71; - margin: 15px; - padding: 8px 6px 8px 6px; - position: absolute; - right: 0; - top: 0; -} -.site-searchbar-toggle.int {margin:0;} -.site-navbar-toggler { - display: inline-block; -} -.application .navbar-light .site-navbar-toggler, -.program-header .navbar-light .side-navbar-toggler { - border: none; -} -.site-navbar-toggler { - font-size: 1.25rem; - line-height: 1; - background-color: transparent; -} -.site-search-bar { - margin: 0; -} -.site-searchbar-form { - font-family: Roboto, "Helvetica Neue", Helvetica, sans-serif; - padding-top: 7px; - padding-bottom: 7px; - background-color: transparent; -} -.site-searchbar-form .form-control { - display: inline-block; - width: 200px; - vertical-align: middle; - box-shadow: none; - height: 40px; -} -@media (max-width: 350px) { - .site-searchbar-form .form-control { - width: 160px; - } -} -.site-searchbar-form .form-group {margin-bottom:0;} -#searchbar-collapse-mobile button { - background-color: #D1D5D8; - border-color: #D1D5D8; - color: #333; - line-height:1.7; -} - -@media (min-width: 576px) { - #searchbar-collapse-mobile button {margin-left:3px;} -} - -/* Medium devices (tablets, 768px and up) The navbar toggle appears at this breakpoint */ -@media (min-width: 768px) { - .site-searchbar-toggle { - position: relative; - } - .site-searchbar-form {float:right;} - .site-search-bar { - background-color: #e3e3e3; - } -} diff --git a/frontend/client/css/nrel.application.min.css b/frontend/client/css/nrel.application.min.css deleted file mode 100755 index 93f25d5..0000000 --- a/frontend/client/css/nrel.application.min.css +++ /dev/null @@ -1 +0,0 @@ -body, html {overflow-x:hidden;}#content {margin-top:.5em }header .logo{background-position:center center }@media (min-width:768px){#content {margin-top:1em }header .logo{background-position:center right }.navbar-wrapper .container.app{border-top:none }}header a.app-name,header a.app-name:hover,header a.app-name:visited{color:#4B545A;display:block;font-family:Roboto;font-size:1.5rem;line-height:1.2em;margin:0 0 .3em;text-align:center;text-decoration:none }@media (min-width:768px){header a.app-name,header a.app-name:hover,header a.app-name:visited {font-size:1.85rem;margin-top:28px;text-align:left }}header .logo.interior {height: 65px;background-size: 137px;}#interior.program-header .navbar-light .navbar-nav > li > a {padding: 5px 15px;}@media (min-width: 768px) {.banner-logo {min-height: 75px;}}.site-searchbar-toggle {border: 1px solid #5e6a71;border-radius: 5px;color: #5e6a71;margin: 15px;padding: 8px 6px 8px 6px;position: absolute;right: 0;top: 0;}.site-searchbar-toggle.int {margin:0;}.site-navbar-toggler {display: inline-block;}.application .navbar-light .site-navbar-toggler, .program-header .navbar-light .side-navbar-toggler {border: none;}.site-navbar-toggler {font-size: 1.25rem;line-height: 1;background-color: transparent;}.site-search-bar {margin: 0;}.site-searchbar-form {font-family: Roboto, "Helvetica Neue", Helvetica, sans-serif;padding-top: 7px;padding-bottom: 7px;background-color: transparent;}.site-searchbar-form .form-control {display: inline-block;width: 200px;vertical-align: middle;box-shadow: none;height: 40px;}@media (max-width: 350px) {.site-searchbar-form .form-control {width: 160px;}}.site-searchbar-form .form-group {margin-bottom:0;}#searchbar-collapse-mobile button {background-color: #D1D5D8;border-color: #D1D5D8;color: #333;line-height:1.7;}@media (min-width: 576px) {#searchbar-collapse-mobile button {margin-left:3px;}}@media (min-width: 768px) {.site-searchbar-toggle {position: relative;}.site-searchbar-form {float:right;}.site-search-bar {background-color: #e3e3e3;}} \ No newline at end of file diff --git a/frontend/client/css/nrel.complete.css b/frontend/client/css/nrel.complete.css deleted file mode 100755 index 0f7f852..0000000 --- a/frontend/client/css/nrel.complete.css +++ /dev/null @@ -1,2651 +0,0 @@ -@charset "UTF-8"; -/* CSS Document */ - -body, html {overflow-x:hidden;} -body { - background-color: transparent; - color: #222; - font-family:'Roboto', 'Helvetica Neue', Helvetica, sans-serif; - font-weight: 400; - line-height: 1.65; - padding: 0; -} -h1 { - color:#222; - font-weight: 300; - font-size: 2em; - margin-top: 0; -} -@media (min-width: 768px) { - h1 {font-size: 2.15em;} -} -h2 { - color: #e07700; - font-weight: 300; - font-size: 1.75em; -} -h3 { - color:#222; - font-weight: 400; - font-size: 1.5em; -} -h4 { - color: #222; - font-size: 1.375em; - font-weight: 400; -} -h5 { - color:#222; - font-weight: 400; - font-size: 1.25em; -} -h6 { - color:#222; - font-weight: 400; - font-size: 1.125em; -} -a { - color: #0071b8; -} -a:visited { - color: #9650b9; -} -a:hover, a:active { - color: #0071b8; - text-decoration: underline; -} -.blue a:visited { - color: #0071b8; -} -.green a:visited { - color: #5D9732; -} -a.tile { - display: block; -} -a.tile:hover { - text-decoration: none; -} -a.fa { - text-decoration: none; -} -a.fa:hover { - text-decoration: underline; -} -h1 a[href]::after, -h2 a[href]::after, -h3 a[href]::after, -h4 a[href]::after, -h5 a[href]::after, -h6 a[href]::after { - font-family: FontAwesome; - content: "\00a0\f054"; - display: inline; - font-size: 0.65em; -} -a[href]::after { - font-family: FontAwesome; - font-size: 0.85em; -} - -h1 a[href$=".pdf"]::after, -h2 a[href$=".pdf"]::after, -h3 a[href$=".pdf"]::after, -h4 a[href$=".pdf"]::after, -h5 a[href$=".pdf"]::after, -h6 a[href$=".pdf"]::after, -a[href$=".pdf"]::after { - content: "\00a0\f1c1"; - display: inline; -} - -h1 a[href$=".doc"]::after, -h2 a[href$=".doc"]::after, -h3 a[href$=".doc"]::after, -h4 a[href$=".doc"]::after, -h5 a[href$=".doc"]::after, -h6 a[href$=".doc"]::after, -a[href$=".doc"]::after { - content: "\00a0\f1c2"; - display: inline; -} - -h1 a[href$=".xls"]::after, -h2 a[href$=".xls"]::after, -h3 a[href$=".xls"]::after, -h4 a[href$=".xls"]::after, -h5 a[href$=".xls"]::after, -h6 a[href$=".xls"]::after, -a[href$=".xls"]::after{ - content: "\00a0\f1c3"; - display: inline; -} - -h1 a[href$=".ppt"]::after, -h2 a[href$=".ppt"]::after, -h3 a[href$=".ppt"]::after, -h4 a[href$=".ppt"]::after, -h5 a[href$=".ppt"]::after, -h6 a[href$=".ppt"]::after, -a[href$=".ppt"]::after { - content: "\00a0\f1c4"; - display: inline; -} - -h1 a[href$=".jpg"]::after, -h2 a[href$=".jpg"]::after, -h3 a[href$=".jpg"]::after, -h4 a[href$=".jpg"]::after, -h5 a[href$=".jpg"]::after, -h6 a[href$=".jpg"]::after, -h1 a[href$=".png"]::after, -h2 a[href$=".png"]::after, -h3 a[href$=".png"]::after, -h4 a[href$=".png"]::after, -h5 a[href$=".png"]::after, -h6 a[href$=".png"]::after, -h1 a[href$=".gif"]::after, -h2 a[href$=".gif"]::after, -h3 a[href$=".gif"]::after, -h4 a[href$=".gif"]::after, -h5 a[href$=".gif"]::after, -h6 a[href$=".gif"]::after, -a[href$=".jpg"]::after, a[href$=".png"]::after, a[href$=".gif"]::after { - content: "\00a0\f1c5"; - display: inline; -} - -h1 a[href$=".mp4"]::after, -h2 a[href$=".mp4"]::after, -h3 a[href$=".mp4"]::after, -h4 a[href$=".mp4"]::after, -h5 a[href$=".mp4"]::after, -h6 a[href$=".mp4"]::after, -a[href$=".mp4"]::after { - content: "\00a0\f03d"; - display: inline; -} - -h1 a[href$=".txt"]::after, -h2 a[href$=".txt"]::after, -h3 a[href$=".txt"]::after, -h4 a[href$=".txt"]::after, -h5 a[href$=".txt"]::after, -h6 a[href$=".txt"]::after, -a[href$=".txt"]::after { - content: "\00a0\f15b"; - display: inline; -} - -h1 a[href$=".zip"]::after, -h2 a[href$=".zip"]::after, -h3 a[href$=".zip"]::after, -h4 a[href$=".zip"]::after, -h5 a[href$=".zip"]::after, -h6 a[href$=".zip"]::after, -a[href$=".zip"]::after { - content: "\00a0\f1c6"; - display: inline; -} -img a[href]::after, -img a[href$=".pdf"]::after, -img a[href$=".doc"]::after, -img a[href$=".xls"]::after, -img a[href$=".ppt"]::after, -img a[href$=".jpg"]::after, -img a[href$=".png"]::after, -img a[href$=".gif"]::after, -img a[href$=".mp4"]::after, -img a[href$=".txt"]::after, -img a[href$=".zip"]::after {content:none;} -a.noicon::after {content:none;} -#content { - padding-bottom: 5em; -} -@media (min-width: 768px) { - body.layout-12 > #page-tools .row > .col-sm-9 { - width: 100%; - } - body.layout-12 > #page-tools .row > .col-sm-offset-3 { - margin-left:0; - } -} -.list-headline { - border-bottom: 1px solid #000; - padding-bottom: 10px; -} -.lead { - font-weight: 300; - font-size: 1.15em; - line-height: 1.4em; -} -.banner-site { - font-size: 1.5rem; - line-height: 1.2em; - font-weight: 400; - margin: 0 0 .3em; - text-align: center; - text-decoration: none; -} -@media (min-width: 768px) { - .banner-site { - font-size: 2rem; - font-weight: 300; - text-align: left; - margin: 5px 0; - } -} -.banner-site a, .banner-site a:hover { - color: #000; - text-decoration: none; -} -.banner-site a:visited { - color: #4B545A; - text-decoration: none; -} -.navbar-light .navbar-brand {color:#4B545A;} -.breadcrumb { - background-color: transparent; - font-size: 14px; - margin-bottom: 1em; - padding-left:0; -} -.breadcrumb > li + li:before { - content: "\00BB"; - padding: 0 5px; - color: #000; -} -.breadcrumb a, .breadcrumb a:visited { - color: #0071b8; - text-decoration: none; -} -nav.breadcrumb.invisible { - margin-top: -50px; -} -.hide { - display: none !important; -} -.sidenav { - margin: 2em 0; -} -.sidenav ul.nav li { - font-weight: 400; - line-height: 1.3; - width:100%; -} -.sidenav a, .sidenav a:visited { - color:#0079C2; - text-decoration: none; -} -.nav>li>a:hover, .nav>li>a:focus { - background-color: transparent; -} -@media (min-width: 992px) { - .sidenav { - margin: 0.6em 0; - } -} -.sidenav > ul.nav > li:first-child > a { - border-top: 3px solid #D1D5D8; -} -.sidenav > ul.nav > li > a { - background-color: transparent; - border-bottom:1px solid #d1d5d8; - border-top: 0; -} -.sidenav > ul.nav > li > a:hover, .sidenav > ul.nav > li > a:focus, .sidenav > ul.nav > li.active > a, .sidenav > ul.nav > li.active > a:visited { - background-color: #0079c2; - color: #fff; -} -.sidenav > ul.nav ul.nav li > a { - padding-left: 30px; - border-bottom: 1px dashed #d1d5d8; -} -.sidenav > ul.nav ul.nav li:last-child > a { - border-bottom: 1px solid #d1d5d8; -} -.sidenav > ul.nav ul.nav li > a:hover, .sidenav > ul.nav ul.nav li > a:focus, .sidenav > ul.nav ul.nav li.active > a, .sidenav > ul.nav ul.nav li.active > a:visited { - background-color: #E5F1F9; - color: #0064A2; -} -.sidenav ul.nav ul.nav ul.nav li { - color: #0079c2; - display: list-item; -} -.sidenav ul.nav ul.nav ul.nav li > a:before { - content: '\25A0\00a0'; - vertical-align: text-bottom; -} -.sidenav ul.nav ul.nav ul.nav li > a { - border-bottom: none; - margin-left: 1em; - text-indent: -0.85em; -} -.sidenav ul.nav ul.nav ul.nav li:last-child > a { - border-bottom: 1px dashed #d1d5d8; -} -.sidenav ul.nav ul.nav ul.nav li:hover, .sidenav ul.nav ul.nav ul.nav li:focus, .sidenav ul.nav ul.nav ul.nav li.active { - background-color: #E5F1F9; - color: #0064A2; -} -h1.green, h2.green, h3.green, h4.green, h5.green, h6.green, p.green { - color: #5D9732; -} -div.green { - background-color: #5D9732; -} -h1.grey, h1.gray, h2.grey, h2.gray, h3.grey, h3.gray, h4.grey, h4.gray, h5.grey, h5.gray, h6.grey, h6.gray, p.grey, p.gray { - color: #5E6A71; -} -div.grey, div.gray { - background-color: #5E6A71; - color: #fff; -} -h1.blue, h2.blue, h3.blue, h4.blue, h5.blue, h6.blue, p.blue { - color: #0079c2; -} -div.blue { - background-color: #0079C2; - color: #fff; -} -h1.black, h2.black, h3.black, h4.black, h5.black, h6.black, p.black { - color: #000; -} -div.black { - background-color: #000; - color: #fff; -} -h1 a, h2 a, h3 a, h4 a, h5 a, h6 a { - text-decoration: none; -} -.allcaps { - text-transform: uppercase; -} -.nomargin + h2, .nomargin + h3, .nomargin + h4, .nomargin + h5, .nomargin + h6 { - margin-top: 0; -} -.smaller {font-size:90%;} - -/* CARDS */ -.card {display:block;} -.bg-blue .card-title { - color:#fff; -} -.headerlink-cr::after { - font-family: FontAwesome; - content: "\00a0\f054"; - display: inline; - font-size: 0.65em; -} -.card-header { - background-color: #fff; - border-bottom: 6px solid #E66D0A; -} -.card-header h2 { - font-size: 1.5em; - font-weight: 400; - margin:0; -} -.card-header h2 a:link, .card-header h2 a:visited, -.card-header h3 a:link, .card-header h3 a:visited { - color:#212121; -} -.card-header h2 a:hover, .card-header h2 a:focus, -.card-header h3 a:hover, .card-header h3 a:focus { - color:#cc6600; -} -.card-header.orange { - background-color: #fff; - border-bottom: 6px solid #E66D0A; -} -.card-text { - line-height: 1.35em; -} -.card-title {margin-bottom:0;} -.card-footer {background-color: #fff;} -.fw-feat { - background-color: #fff; - background-clip: border-box; - border: 1px solid rgba(0,0,0,.125); - border-radius: .25rem; - width:100%; -} -.fw-feat h3 a:link, .fw-feat h2 a:link, -.fw-feat h3 a:visited, .fw-feat h2 a:visited { - color:#000; - text-decoration: none; -} -.fw-feat h3 a:hover, .fw-feat h2 a:hover, -.fw-feat h3 a:active, .fw-feat h2 a:active { - color:#e07700; - text-decoration: none; -} -.fw-feat-info {padding:1em 1em 0 1em; border-bottom: 6px solid #E66D0A; border-top:none;} -/* Large Devices, Wide Screens */ -@media only screen and (min-width : 1200px) { - .overlay-text { - top: 65px; - } - .fw-feat-info {border-bottom:none; border-top: 6px solid #E66D0A;} -} - -/* secondary page features */ -.card.bg-secondary .card-body {border-top: 6px solid #e07700;} -.card.bg-secondary .card-body h2, -.card.bg-secondary .card-body h3, -.card.bg-secondary .card-body h4 { - color:#F5F5F5; -} - -/* orange navigation cards/blocks */ -.orange-card .card-body { - border-top: 6px solid #e07700; - font-size: 1rem; -} -.orange-card h3 {font-weight:400;} -.orange-card h4 a:link, .orange-card h4 a:visited {color:#e07700;font-weight:300;} -.orange-card h4 a:hover, .orange-card h4 a:active {color:#0071b8;font-weight:300;} -.img-feat-orange { - background: rgba(255, 255, 255, 0.8); - border-bottom: 6px solid #E66D0A; - position: absolute; - bottom: 0; - left: 0; - padding: 1em; - margin:0; - width: 100%; -} -.img-feat-orange a:link, -.img-feat-orange a:visited { - color:#000; -} -.img-feat-orange a:active, -.img-feat-orange a:hover { - color:#e07700; -} - -/* cards with color borders and icons */ -.border-danger {border-color:#D9531E !important;} -.border-danger .card-header { - background-color: #D9531E; - border-bottom:none; -} -.border-primary {border-color:#0079C2 !important;} -.border-primary .card-header { - background-color: #0079C2; - border-bottom:none; -} -.border-success {border-color:#5D9732 !important;} -.border-success .card-header { - background-color: #5D9732; - border-bottom:none; -} -.border-warning {border-color:#F7A11A !important;} -.border-warning .card-header { - background-color: #F7A11A; - border-bottom:none; -} -.border-danger .card-header h3, .border-primary .card-header h3, .border-success .card-header h3, .border-warning .card-header h3 {color:#fff;} -.card-title img {width: 30px; margin-bottom:5px; margin-right: 6px;} - -/* Vision callout boxes */ -/* snippets with images */ -.visionbox.main {background-color:#0079C2;color:#fff;} -.visionbox.ceem {background-color:#FFC432;color:#3A4246;} -.visionbox.e2m {background-color:#933C06;color:#fff;} -.visionbox.iep {background-color:#5D9732;color:#fff;} -.visiontext {padding:1em;} -.visionlink {font-weight: 300;font-size:1.35em;line-height:1.1em;} -.main .visionlink a:link, .main .visionlink a:visited, -.e2m .visionlink a:link, .e2m .visionlink a:visited, -.iep .visionlink a:link, .iep .visionlink a:visited {color:#fff;} -.ceem .visionlink a:link, .ceem .visionlink a:visited {color:#3A4246;} -/* assets with icons */ -.visionicon {height:90px; margin:0.35em auto;} -.vision h3 a:link, .vision h3 a:visited {color:#333;} -.vision h3 a:hover, .vision h3 a:active {color:#333;text-decoration:underline;} -.iep-top {background-color:#8CC63F;text-align:center;} -.iep-text {border:1px solid #8CC63F;padding:1em;} -.e2m-top {background-color:#D9531E;text-align:center;} -.e2m-text {border:1px solid #D9531E;padding:1em;} -.ceem-top {background-color:#F7A11A;text-align:center;} -.ceem-text {border:1px solid #F7A11A;padding:1em;} -.nrelvision-top {background-color:#E9ECEF;text-align:center;} -.nrelvision-text {border:1px solid #E9ECEF;padding:1em;} - - -/* Set width to make card deck cards 100% width */ -@media (max-width: 768px) { - - .card-deck-wrapper {margin-right:0;margin-left:0;} - .card-deck { - display: block; - width: 100%; - margin-bottom: .75rem; - table-layout: fixed; - border-spacing: 1.25rem 0; - } - .card-deck .card { - display: block; - margin-bottom: .75rem; - vertical-align: top; - } -} - -/* CARDS/ACCORDIONS */ - -#accordion .card, .accordion .card, -[id^="accordion_"] .card, .accordion .card { - border: 1px solid #0079C2; - border-radius: 0; - margin: -1px -1px 0.5rem -1px; -} -#accordion .card-header, .accordion .card-header, -[id^="accordion_"] .card-header, .accordion .card-header { - padding: 0; - margin: 0; - border-radius: 0; - border-bottom: none; -} -#accordion .card-header .btn, .accordion .card-header .btn, -[id^="accordion_"] .card-header .btn, .accordion .card-header .btn { - text-transform: none; - font-size: 1.3rem; - font-weight: 300; - width:100%; - text-align: left; - border-radius: 0; - line-height: 1; - padding: 0.65rem 0.65rem 0.65rem 40px; -} -#accordion .card-header .btn-link:hover, -[id^="accordion_"] .card-header .btn-link:hover, -#accordion .card-header .btn-link:active, -[id^="accordion_"] .card-header .btn-link:active, -#accordion .card-header .btn-link:focus, -[id^="accordion_"] .card-header .btn-link:focus, -.accordion .card-header .btn-link:hover, -.accordion .card-header .btn-link:active, -.accordion .card-header .btn-link:focus { - color: #0079C2; - text-decoration: none; -} -/* #accordion .card-header [data-toggle="collapse"] .fa:before { -content: "\f056"; -} -#accordion .card-header [data-toggle="collapse"].collapsed .fa:before { -content: "\f055"; -} */ -#accordion .card-header .btn[aria-expanded="true"], -[id^="accordion_"] .card-header .btn[aria-expanded="true"], -.accordion .card-header .btn[aria-expanded="true"] { - background-color: #0079C2; - color: #fff; - background-image: url('client/img/icon_minus.svg'); - background-repeat: no-repeat; - background-position: 1% center; - background-size: 25px 25px; - margin-left: 0px; - padding-left: 40px; - display: block; -} -#accordion .card-header .btn[aria-expanded="false"], -[id^="accordion_"] .card-header .btn[aria-expanded="false"], -.accordion .card-header .btn[aria-expanded="false"] { - background-color: #fff; - color: #0079C2; - background-image: url('client/img/icon_plus_blue.svg'); - background-repeat: no-repeat; - background-position: 1% center; - background-size: 25px 25px; - margin-left: 0px; - padding-left: 40px; - display: block; -} -#accordion .card-header .btn:hover, #accordion .card-header .btn:focus, -[id^="accordion_"] .card-header .btn:hover, [id^="accordion_"] .card-header .btn:focus { - background-color: #0079C2; - color: #fff; - background-image: url('/_resources/images/icon_minus.svg'); - background-repeat: no-repeat; - background-position: 1% center; - background-size: 25px 25px; -} -.accordion > .card:not(:first-of-type):not(:last-of-type), -.accordion > .card:first-of-type {border-bottom: 1px solid #0079C2;} - -/* striped list cards */ -.nrel-list .card-header h3 a:hover, .nrel-list .card-header h3 a:focus { - color: #303030; - text-decoration:underline; -} -.nrel-list .card-header span a:link, .nrel-list .card-header span a:visited { - color:#212121; - text-decoration:underline; - font-size: 0.9em; -} -.nrel-list .card-header span a:hover, .nrel-list .card-header span a:focus { - color:#F5F5F5; -} -ul.list-group>li:nth-child(odd){ - background-color:#F5F5F5; -} -.nrel-list .card-header { - background-color: #80D0FF; - border-bottom: 1px solid rgba(0,0,0,.125); -} -.nrel-list .card-header h3 { - font-size: 1.35em; - font-weight:400; - margin:0; -} -.nrel-list .list-group-item .date { - font-size: 0.85em; - margin: 0.3em 0 0 0; - font-weight: 300; -} -.nrel-list .list-group-item p, -.list-group-item { - font-weight: 400; - line-height: 1.2; - margin:0; -} - -/* Program site home page features */ -.feature-teaser { - background-color: #F5F5F5; - padding: 1.5em; - height:100%; -} -.feature-teaser.ft-blue { - background-color: #52BFFF; - color:#333; -} -.feature-teaser h1 { - font-size: 2em; -} -.feature-teaser h1 strong { - font-weight: bold; -} -.feature-teaser p.lead { - font-size: 1rem; -} -.feature-teaser.ft-blue a:link, -.feature-teaser.ft-blue a:visited { - color: #000; -} -@media (min-width: 768px) { - .feature-teaser h1{ - font-size: 3em; - } - .feature-teaser p.lead { - font-size: 1.25rem; - } -} -@media (min-width: 992px) { - .feature-teaser h1{ - font-size: 2em; - } - .feature-teaser p.lead { - font-size: 1rem; - } -} -@media (min-width: 1200px) { - .feature-teaser { - padding: 2em; - } - .feature-teaser h1 { - font-size: 2.5rem; - } - .feature-teaser p.lead { - font-size: 1.25rem; - } -} - -.big-quote::before { - color: #ccc; - font-family: serif; - font-size: 3em; - line-height: 0; - vertical-align: text-bottom; - content:"\201C"; - margin-right:3px; - float: left; - margin-top: -25px; - padding-top: 45px; -} -.header-description { - color:#757575; - font-size: 1rem; - font-weight: 400; - margin-top: 0; -} -h2 .fa, h3 .fa, h4 .fa, h5 .fa, h6 .fa, .header .fa { - font-size: 80%; - vertical-align:middle; -} -.headerlink:after { - font-family: FontAwesome; - content: "\00a0\f054"; - display: inline; - font-size: 0.65em; -} -.more, .learn-more { - font-weight: 400; -} -.more a, .more a:visited, .learn-more a, .learn-more a:visited { - color:#0071b8; - text-decoration: none; -} -.more a:hover, .learn-more a:hover { - text-decoration: underline; -} -.nav-tabs { - margin-top:1em; -} -.nav-tabs { - font-size: 21px; - font-weight: 300; -} -.nav-tabs .nav-link { - margin-bottom: -2px; - border-bottom: 1px solid #fff; - border-radius: 7px 7px 0 0; - margin-right:0; - background-color: #fff; - border-color: #e9ecef; -} -.nav-tabs .nav-link.active, -.nav-tabs .nav-link:hover { - border-color: #dee2e6 #dee2e6 #fff; -} -.nav-tabs .nav-item { - margin-bottom: 0; -} -.tab-pane { - padding:2em 1em; -} -.panel { - -webkit-box-shadow: none; - box-shadow: none; -} -.panel-default { - border: 1px solid #127BBF; -} -.panel-body { - padding: 15px; -} -.collapse.in { - display: block; -} -.panel-heading > .panel-title > .accordion-toggle { - margin-left:0px; - padding-left:40px; - display: block; -} -.panel-heading > .panel-title > .accordion-toggle, .panel-heading > .panel-title > .accordion-toggle:visited { - color:#fff; -} -.panel-heading > .panel-title > .accordion-toggle { - background-image: url('client/img/icon_minus.svg'); - background-repeat: no-repeat; - background-position: 1% center; - background-size: 25px 25px; -} -.panel-heading > .panel-title > .accordion-toggle.collapsed { - background-image: url('client/img/icon_plus_blue.svg'); -} -.panel-group .panel { - margin-bottom: 12px; -} -.panel-group .panel + .panel { - margin-top:1px; -} -.panel-group .panel, .panel-group .panel-heading { - border-radius: 1px; -} -.panel-group .panel:first-child { - border-top-left-radius: 4px; - border-top-right-radius: 4px; -} -.panel-group .panel:last-child { - border-bottom-left-radius: 4px; - border-bottom-right-radius: 4px; -} -.panel-heading { - padding: 0; - cursor: pointer; -} -.panel-heading h4 { - margin: 0; -} -h4.panel-title a[href]::after {content:"";} -.panel-default > .panel-heading { - background-color: transparent; - color:#fff; -} -.panel-default > .panel-heading a { - display: block; - padding: 10px 15px; - text-decoration: none; -} -.panel-default > .panel-heading a:hover { - text-decoration: none; - color:#fff; -} - -.panel-group.inverse .panel-heading > .panel-title > a.accordion-toggle, .panel-group.inverse .panel-heading > .panel-title > a.accordion-toggle:visited { - background-color: #127BBF; - color:#fff; -} -.panel-group.inverse .panel-heading > .panel-title > a.accordion-toggle.collapsed, .panel-group.inverse .panel-heading > .panel-title > a.accordion-toggle.collapsed:visited { - background-color: transparent; - color: #127BBF; -} -.content-sandwich:before, .content-sandwich:after { - content: " "; - display: table; -} -.content-sandwich:after { - clear:both; -} -.content-sandwich { - border-top: 5px solid #ccc; - border-bottom: 5px solid #ccc; - margin-top: 1.65em; - margin-bottom: 1.65em; - padding: 1em 0; -} -.content-sandwich-topper + .content-sandwich { - margin-top: 0; -} -.content-sandwich p { - font-size: 16px; - font-weight: normal; -} -.content-sandwich .headline, .content-sandwich .header { - font-size: 18px; - font-weight: 600; - margin-bottom: 0; - margin-top: 0; -} -.content-sandwich .headline ~ p, .content-sandwich .header ~ p, .content-sandwich h1 ~ p, .content-sandwich h2 ~ p, .content-sandwich h3 ~ p, .content-sandwich h4 ~ p, .content-sandwich h5 ~ p, .content-sandwich h6 ~ p { - margin-top: 0; -} -.content-sandwich .item { - border-bottom: 1px solid #ccc; - padding: 1em 0; -} -.content-sandwich .item:last-child { - border-bottom: none; -} -.content-sandwich .item p { - margin-bottom: 0; -} -.content-sandwich ul { - font-size: 16px; - font-weight: normal; -} -.content-sandwich hr { - border-top: 1px solid #ccc; -} -.content-sandwich img { - margin: 0 auto; -} -.content-sandwich.vertical-aligned { -} -.content-sandwich.vertical-aligned [class*="col-"] .icon, .content-sandwich.vertical-aligned [class*="col-"] > *:only-child { - margin-top: 1.5em; -} -@media(min-width:768px) { - .content-sandwich.vertical-aligned [class*="col-"] .icon, .content-sandwich.vertical-aligned [class*="col-"] > *:only-child { - margin-top: .75em; - } -} -@media(min-width:1200px) { - .content-sandwich.vertical-aligned [class*="col-"] .icon, .content-sandwich.vertical-aligned [class*="col-"] > *:only-child { - margin-top: 1.5em; - } -} -.content-sandwich.quicklinks .icon { - margin-top: 1em; -} -.content-sandwich.quicklinks .header { - margin-top: 1em; -} -@media (min-width: 992px) { - .content-sandwich.quicklinks .header { - margin-top: 1.65em; - } -} -.bio { - padding-bottom: 2em; -} -.bio:last-child { - border-bottom: none; -} -.bio h3.header-description, .bio h4.header-description, .bio p.header-description { - font-size: 1rem; - color:#757575; - font-weight:400; - margin: 0 0 0.25em 0; -} -.bio p.header {font-size:1.3em;font-weight:300;line-height: 1.2;margin:0 0 0.25em 0;} -.bio p.header a[href]::after { - font-family: FontAwesome; - content: "\00a0\f054"; - display: inline; - font-size: 0.65em; -} -#staff_wrapper input { - margin-bottom:0.5em; -} -#staff .bg-blue { - color: #fff; - background-color: #0079C2; -} -table#staff td { - text-align: left; -} -table#staff td.child ul { - padding-left: 1.2em; -} -table#staff td.child ul li { - text-align: left; - list-style: none; - margin-left: 0; -} -.feature { - border-bottom: 1px solid #000; - display: block; - margin-bottom:30px; - min-height: 250px; - text-decoration: none; -} -@media(min-width:768px){ - .feature { - margin-bottom:12px; - } -} -.row .col-sm-6:last-child .feature.last { - border-bottom: none; -} -@media(min-width:768px){ - .feature.last { - border-bottom: none; - } -} -@media (min-width: 992px){ - .feature { - min-height: 260px; - } -} -@media (min-width: 1200px){ - .feature { - min-height: 290px; - } -} -.feature:hover { - text-decoration: none; -} -.feature:hover .headline { - color:#e07700; -} -.feature .category { - color: #5E6A71; - font-size: 16px; - font-weight: 700; - margin-bottom: 5px; - margin-top: 10px; - text-transform: uppercase; -} -.feature .headline { - color: #0079C2; - font-weight: 400; - font-size: 1.4em; - line-height: 1.2; - margin-top:0; -} -.feature-secondary .link-tile { - margin-top: 24px; -} -.feature-secondary .link-tile + .link-tile { - margin-top:24px; -} -.showcase { - opacity: 1; - padding-bottom: 2em; - padding-top: 2em; -} -.showcase .caption { - background-color: rgba(0,0,0,.8); - box-sizing: border-box; - color: #fff; - font-weight: 300; - line-height: 1.1; - padding: .5em 1em; - position:relative; -} -.showcase .link-tile, .showcase .link-tile { - position: relative; -} -.showcase .feature-primary .caption, .showcase .feature-secondary .caption { - font-size: 30px; -} -.modal-footer { - text-align: left; -} -@media(min-width:768px){ - .showcase { - height:431px; - } - .showcase .caption { - left: 0; - right: 0; - bottom: 0; - position: absolute; - } - .feature-secondary .link-tile { - margin-top: 0px; - } - .showcase .feature-primary .caption { - font-size: 20px; - } - .showcase .feature-secondary .caption { - font-size: 14px; - } -} -@media(min-width:992px){ - .showcase { - height:545px; - } -} -@media(min-width:1200px){ - .showcase { - height:649px; - } - .showcase .feature-primary .caption { - font-size: 30px; - } - .showcase .feature-secondary .caption { - font-size: 20px; - } -} -.modal.fade .modal-dialog { - transform: none !important; - -webkit-transition: -webkit-transform 0.3s ease-out; - -moz-transition: -moz-transform 0.3s ease-out; - -o-transition: -o-transform 0.3s ease-out; - transition: transform 0.3s ease-out; -} -.modal.in .modal-dialog { - transform: none !important; -} -span.required { - font-weight: normal; -} -select, select.form-control { - background-image: linear-gradient(#FFF,#E6E6E6); - box-shadow: inset 0 1px #FFF,inset 0 0 0 1px rgba(255, 255, 255, 0.5),0 1px 2px rgba(0, 0, 0, 0.1); -} -.dropdown-menu .divider { - width: auto; - border-top: none; - margin: 9px auto; - padding: 0; -} -a.btn, a.btn:visited, .btn a:hover { - color: #fff; - text-decoration: none; -} -a.btn-link, a.btn-link:visited { - color: #0079C2; - text-decoration: none; -} -.btn { - font-weight: 400; - min-height:40px; - text-transform: uppercase; - line-height: 1.8; -} -a.btn-default { - color:#333; -} -.btn-default { - color: #333; - background-color: #fff; - border-color: #ccc; -} -.btn-default:hover, .btn-default:focus, .btn-default:active, .btn-default.active, .open > .dropdown-toggle.btn-default { - color: #333; - background-color: #e6e6e6; - border-color: #adadad; - -webkit-box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.4); - -moz-box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.4); - box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.4); -} -.btn-primary { - color: #fff; - background-color: #0079c2; - border-color: #0079c2; -} -.btn-primary:hover, .btn-primary:focus, .btn-primary:active, .btn-primary.active, .open > .dropdown-toggle.btn-primary { - color: #b7d6e9; - background-color: #3071a9; - border-color: #285e8e; - -webkit-box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.4); - -moz-box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.4); - box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.4); -} -.btn-success { - color: #fff; - background-color: #4c8224; - border-color: #4c8224; -} -.btn-success:hover, .btn-success:focus, .btn-success:active, .btn-success.active, .open > .dropdown-toggle.btn-success { - color: #d2e3c5; - background-color: #3b651c; - border-color: #3b651c; - -webkit-box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.4); - -moz-box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.4); - box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.4); -} -.btn-info { - color: #fff; - background-color: #5e6a71; - border-color: #5e6a71; -} -.btn-info:hover, .btn-info:focus, .btn-info:active, .btn-info.active, .open > .dropdown-toggle.btn-info { - color: #cccccc; - background-color: #3d4449; - border-color: #3d4449; - -webkit-box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.4); - -moz-box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.4); - box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.4); -} -.btn-warning { - color: #000; - background-color: #f7a11a; - border-color: #f7a11a; -} -.btn-warning:hover, .btn-warning:focus, .btn-warning:active, .btn-warning.active, .open > .dropdown-toggle.btn-warning { - color: #2d1c00; - background-color: #b87813; - border-color: #b87813; - -webkit-box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.3); - -moz-box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.3); - box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.3); -} -.btn-danger { - color: #fff; - background-color: #933c06; - border-color: #933c06; -} -.btn-danger:hover, .btn-danger:focus, .btn-danger:active, .btn-danger.active, .open > .dropdown-toggle.btn-danger { - color: #dac4b7; - background-color: #702e05; - border-color: #702e05; - -webkit-box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.4); - -moz-box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.4); - box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.4); -} -.btn-long { - font-size:16px; - height: 38px; - line-height: 1.3em; - white-space: normal; -} -.ou-form .btn { - display:inline-block; - padding:6px 12px; - margin-bottom:0; - font-weight:400; - text-align:center; - white-space:nowrap; - vertical-align:middle; - cursor:pointer; - border:1px solid transparent; - border-radius:4px; - color:#fff; - text-transform: uppercase; -} -.ou-form .btn-success:active,.ou-form .btn-success:hover { - color: #d2e3c5; - background-color: #3b651c; - border-color: #3b651c; - -webkit-box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.4); - -moz-box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.4); - box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.4); -} -@media only screen and (min-width : 992px) { - .btn-long { - height: 80px; - } -} -@media only screen and (min-width : 1200px) { - .btn-long { - height: 60px; - } -} -select.form-control, input.form-control { - height: 40px; -} -@media(min-width:768px){ - .row-eq-height { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - } - .row-eq-height [class^="col-"] { - min-height: 270px; - } - .row-eq-height .sink { - bottom:0; - left: 15px; - right: 15px; - position: absolute; - } -} -.row-highlighted { - background-color: #F5F5F5; - margin-bottom:2em; - margin-top:2em; - padding-bottom:2em; - padding-top:1em; -} -.marketing { - text-align: center; -} -.marketing .img-circle { - margin-top:2em; -} -.marketing h2 { - padding-top:1em; -} -.marketing .btn { - margin-bottom:2em; -} -iframe { - border:0; -} -.video { - margin: 1em 0; -} -.play-overlay { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); -} -.container-xs-height { - display:table; - padding-left:0px; - padding-right:0px; -} -.row-xs-height { - display:table-row; -} -.col-xs-height { - display:table-cell; - float:none; -} -@media (min-width: 768px) { - .container-sm-height { - display:table; - padding-left:0px; - padding-right:0px; - } - .row-sm-height { - display:table-row; - } - .col-sm-height { - display:table-cell; - float:none; - } -} -@media (min-width: 992px) { - .container-md-height { - display:table; - padding-left:0px; - padding-right:0px; - } - .row-md-height { - display:table-row; - } - .col-md-height { - display:table-cell; - float:none; - } -} -@media (min-width: 1200px) { - .container-lg-height { - display:table; - padding-left:0px; - padding-right:0px; - } - .row-lg-height { - display:table-row; - } - .col-lg-height { - display:table-cell; - float:none; - } -} -.col-top { - vertical-align:top; -} -.col-middle { - vertical-align:middle; -} -.col-bottom { - vertical-align:bottom; -} -ul.fa-blue-arrow { - list-style-type: none; -} -ul.fa-blue-arrow li { - margin-bottom: 1em; -} -.fa-blue-arrow a { - margin-left: -5px; - color: #000; -} -.fa-blue-arrow a:visited { - color: #000; -} -.fa-blue-arrow a::before { - font-family: 'FontAwesome'; - content: '\f138'; - margin:0 5px 0 -15px; - color: #0071b8; - text-indent:15px; -} -.fa-blue-arrow a:hover { - text-decoration: none; - color: #0071b8; -} -ul.fa-check-circle:before { - content: ''; -} -ul.fa-check-circle { - list-style-type: none; -} -ul.fa-check-circle li { - margin-bottom: 1em; -} -ul.fa-check-circle li { - margin-left: -5px; - color:#000; -} -ul.fa-check-circle li:before { - color: #0071b8; - content: "\f058"; - font-family: 'FontAwesome'; - margin:0 5px 0 -15px; - text-indent:15px; -} -.list-pipes li + li:before { - content: " | "; -} -.list-pipes li { - padding-left:0 !important; - padding-right:0 !important; -} -.list-links.list-unstyled li { - padding-bottom: 10px; -} -ul.fa-blue-arrow-text { - list-style-type: none; -} -ul.fa-blue-arrow-text li { - margin-bottom: 1em; -} -ul.padded-list li, -ol.padded-list li { - margin-bottom:1.75em; -} -.fa-blue-arrow-text a { - margin-left: -17px; -} -.fa-blue-arrow-text a:before { - font-family: 'FontAwesome'; - content: '\f138'; - margin:0 5px 0 -15px; - color: #0071b8; - text-indent:15px; - display: inline-block; -} -.hero { - margin-bottom: 1em; -} -figcaption, .caption { - font-size: 12px; - color: #757575; -} -.credit { - font-style: italic; -} -a[data-toggle=lightbox] { - text-decoration: none; -} -.enlarge { - display: block; - font-size: 12px; - text-align: right; - text-decoration: none; -} -.enlarge:before { - content: '\f002'; - font-family: 'FontAwesome'; - padding-right: 3px; -} -.border { - border:1px solid #666; -} -a.singleLightbox { - text-decoration: none; -} -a.singleLightbox:visited { - color: #0071b8; -} -a.singleLightbox:hover { - color: #0071b8; - text-decoration: underline; -} -.icon-circle { - height: 100px; - width: 100px; - border-radius: 100px; - background-color: #0071b8; - line-height:100px; - text-align: center; - vertical-align: middle; - color: #FFF; - font-weight: 100; - font-size: 2.2em; - margin:0 auto; -} -@media (min-width: 768px) { - .icon-circle { - margin: auto; - } -} -.icon-circle.green { - background-color: #4c8224; -} -.divider { - width: 12%; - border-top: 1px solid #5D9732; - margin: 0 auto; - padding: 0 0 1.4em 0; -} -.cf:before, .cf:after { - content: " "; - display: table; -} -.cf:after { - clear:both; -} -.clearboth { - clear:both; -} -.wide-80 { - min-width: 80%; -} -.wide-60 { - min-width: 60%; -} -.wide-40 { - min-width: 40%; -} -.wide-20 { - min-width: 20%; -} -.nomargin { - margin-top: 0; - margin-bottom: 0; -} -.notop { - margin-top:0; -} -.padtop { - padding-top: 1em; -} -.padbottom { - padding-bottom: 1em; -} -.clear { - clear: both; -} -.kbd { - padding: 2px 4px; - font-size: 90%; - color: #252525; - background-color: #F5F5F5; - border-radius: 3px; - box-shadow: none; - font-family: Menlo,Monaco,Consolas,"Courier New",monospace; -} -.pull-right { - float: right; -} -.well { - background-color: #F5F5F5; - border-radius: 0; - border:none; - box-shadow: none; - padding: 20px; - margin-bottom: 20px; -} -.well.pull-right { - margin-left:0; - margin-bottom:1em; -} -.well h2 {color:#000;} -@media (min-width: 768px) { - .well.pull-right { - margin-left:1em; - } -} -.content-box { - border: 1px solid #D1D5D8; - padding: 1em; - margin: 0; -} -.actionbox { - border-radius: 0; - border:none; - box-shadow: none; - margin-bottom:2em; - margin-top: 2em; - padding-bottom:1.5em; - padding-top:1.5em; -} -.actionbox.pull-right { - margin-top:0; -} -.actionbox > h2, .actionbox > h3, .actionbox .header { - margin-top:0; -} -.actionbox .btn { - margin-top:.5em; - margin-bottom:.5em; -} -.actionbox.inline { - padding-top:1em; - padding-bottom:1em; -} -.actionbox.inline .header { - margin-top:0; - margin-bottom:.5em; -} -@media (min-width: 768px) { - .actionbox.inline .header { - display: inline; - } -} -.actionbox.inline .btn { - display: block; - margin-bottom:0; - margin-top:0; - max-width: 320px; - margin: 0 auto; -} -@media (min-width: 768px) { - .actionbox.inline .btn { - margin-left:2em; - display: inline; - vertical-align: top; - line-height: 33px; - } -} -.maintenancebox { - border-radius: 0; - border:none; - box-shadow: none; - margin-bottom:1.5em; - margin-top: 1.5em; - padding:1em; -} -.maintenancebox p { - margin:0; - text-align: center; -} -.graphic-box { - position: relative; -} -.caption-box { - background: #000; - background-color: rgba(0, 0, 0, 0.8); - box-sizing: border-box; - color: #fff; - padding:.5em; - text-align: left; - left: 0; - right:0; - bottom:0; - width: 100%; - position: relative; -} -@media (min-width:768px) { - .caption-box { - position: absolute; - padding:1em; - } -} -.caption-box .header, .caption-box h3 { - font-weight: 300; -} -.caption-box .teaser { - line-height: 34px; - margin:0; -} -.caption-box .btn { - text-transform: uppercase; -} -.caption-box a, .caption-box a:visited { - color: #fff; -} -.caption-box .small { - color: inherit; - line-height: inherit; -} -.fileIcon { - margin-left: 5px; -} -.lg-icon { - width:6.5em; -} -.fs-icon, .md-icon { - width:4.5em; -} -.sm-icon { - width:3.5em; -} -.sm-icon-h {height:3.5em;} -.icon-block:hover svg path, -.icon-block:hover h4 a, -.icon-block:hover { - fill: #cc6600; - color: #cc6600; - text-decoration: none; -} -.icon-block h4 {margin-top:0.5em;} -.v-center .row { - display: table; - margin: 0; - table-layout: fixed; - width: 100%; -} -.v-center [class^="col-"] { - display: table-cell; - vertical-align: middle; - float: none; -} -.v-center [class^="col-"] *:first-child { - margin-top: 0; -} -.v-center [class^="col-"] *:last-child { - margin-bottom: 0; -} -.img-center { - margin:0 auto; -} -.img-border{ - border: 1px solid #bebebe; -} -a > .fa:first-child { - margin-right: 5px; - padding-bottom: 3px; -} -a > .fa-ml { - margin-left: 5px; - margin-right:0!important; -} -.addthis_toolbar { - text-align: center; -} -.addthis_toolbar a, .addthis_toolbar a:visited { - color:#000; - line-height: 26px; - text-decoration: none; -} -#feedback-container { - margin-top:3em; - text-align: center; -} -#feedback-container .button-submit { - float:none; -} -#feedback-container textarea { - width: 50%; - margin: 0 auto; -} -.addtocalendar var{ - display: none; -} -.addtocalendar { - position: relative; - display: inline-block; - background: transparent!important; -} -.atcb-link { - display: block; - outline: none!important; - cursor: pointer; -} -.atcb-link:focus~ul, .atcb-link:active~ul, .atcb-list:hover{ - visibility:visible; -} -.atcb-list { - visibility: hidden; - position: absolute; - top: 100%; - left: 0; - width: 170px; - z-index: 900; -} -.atcb-list, .atcb-item { - list-style: none; - margin: 0; - padding: 0; - background: #fff; -} -.atcb-item { - float: none; - text-align: left; -} -.atcb-item-link { - text-decoration: none; - outline: none; - display: block; -} -.atcb-item.hover, .atcb-item:hover { - position: relative; - z-index: 900; - cursor: pointer; - text-decoration: none; - outline: none; -} -.atc-style-menu-wb .atcb-list { - width: 170px; - border: 1px solid rgb(186,186,186); - border-radius: 2px; - box-shadow: 0 0 5px #AAA; -} -.atc-style-menu-wb .atcb-list, .atc-style-menu-wb .atcb-item { - background: #fff; - color: #000; -} -.atc-style-menu-wb .atcb-item, .atc-style-menu-wb .atcb-item-link { - line-height: 1.3em; - vertical-align: middle; - zoom: 1; -} -.atc-style-menu-wb .atcb-item-link, .atc-style-menu-wb .atcb-item-link:hover, .atc-style-menu-wb .atcb-item-link:active, .atc-style-menu-wb .atcb-item-link:focus { - color: #000; - font-family: "Verdana"; - font-size: 14px; - text-decoration: none; - outline: none; - padding: 5px 15px; -} -.atc-style-menu-wb .atcb-item-link:hover, .atc-style-menu-wb .atcb-item-link:active, .atc-style-menu-wb .atcb-item-link:focus { - color: #fff; -} -.atc-style-menu-wb .atcb-item.hover, .atc-style-menu-wb .atcb-item:hover { - background: rgb(66,129,244); -} -.press { - line-height: 1.3; -} -.press .header { - display: table; - border-bottom: 1px solid #000; - margin-bottom: 7px; - width: 100%; -} -.press .more-link { - display: table-cell; - font-size:14px; - text-align: right; -} -.press .more-link a { - text-decoration: none; -} -.press .headline { - color: #0079C2; - font-weight: 400; - line-height: 1.2; - margin: 0; -} -.press .headline + .headline { - margin-top: 10px; -} -.date {margin-bottom:0;} -.press .date { - font-size: 0.85em; - font-weight: 300; - margin: 0.3em 0 1.5em 0; -} -.event .date { - margin-bottom:0; -} -.press .more, .press img { - display: none; -} -.press img.rss { - display: inline; -} -.item .date { - font-size: 1.1em; - font-weight: 500; - margin-top: 2em; -} -.item .date + .headline { - margin-top: .25em; -} -.event-list { - list-style-type: none; - margin-left: 0; - padding-left: 0; -} -.event-list > li { - margin-bottom:1.5em; -} -.event-list .date { - font-weight: bold; - margin-bottom: 0; - text-transform: uppercase; -} -.event-list .event { - font-weight: bold; - line-height:1.3; - margin-bottom: 0; -} -.event-list .location { - line-height:1.3; - margin-bottom: 0; -} -.event-list .addtocalendar > a { - text-transform: uppercase; - font-size: small; -} -div.event { - margin-bottom:10px; -} -.marketing-stack { - display: table; - margin-bottom: 2em; - min-height:555px; - text-align: center; -} -.marketing-stack > div { - display: table-row; -} -.marketing-stack > div > div { - display: table-cell; -} -.marketing-stack > div:first-child > div { - vertical-align: top; -} -.marketing-stack > div:last-child > div { - vertical-align: bottom; -} -@media(min-width:768px) { - .marketing-stack .img-fluid { - width:100%; - } -} -.media-block { - margin-top: 1em; - margin-bottom: 1em; - font-size:1rem; -} -.media-block:after { - clear:both; -} -.media-block h4 { - margin:0.25em 0; -} -.media-block .header, .media-block h2.header, .media-block h3.header, .media-block h4.header, .media-block h5.header, .media-block h6.header { - margin-top:0; -} -.media-block img + .header, .media-block img + h2.header, .media-block img + h3.header, .media-block img + h4.header { - margin-top:1em; -} -.media-block .more a { - text-decoration: none; -} -.media-block .more a:hover { - text-decoration: underline; -} -.media-block img + ul, .media-block img + ol, .media-block img + p { - margin-top: 0.5em; -} -.link-tile { - text-decoration: none; -} -.link-tile { - display: block; -} -.link-tile:hover { - text-decoration: none; -} -.link-tile a { - text-decoration: none; -} -.link-tile a:hover { - text-decoration: none; -} -.link-tile:hover h2, .link-tile:hover h4 { - color:#e07700; -} -.footnote { - border-top: 1px solid #ccc; - font-style: italic; - margin-top: 1.62em; - padding-top: 1em; -} -.table-sm-data, .table-data, .table.data { - font-size: 80%; -} -.table-inline, .table.inline { - display: inline; -} -.table-striped > tbody > tr:nth-child(2n+1) > td, .table-striped > tbody > tr:nth-child(2n+1) > th { - background-color: #F5F5F5; -} -caption { - font-size:22px; - text-align:left; - border-bottom: 3px solid #52BFFF; - color: #004677; - caption-side: top; - padding:0; -} -table.dataTable.dtr-inline.collapsed > tbody > tr[role="row"] > td:first-child::before, table.dataTable.dtr-inline.collapsed > tbody > tr[role="row"] > th:first-child::before { - background-color:#0079c2 !important; -} -#scrollUp { - bottom: 0; - right: 0; - padding: 10px 20px; - background-color: #0079c2; - color: #fff; - text-decoration: none; -} -.navbar { - padding:0; -} -.dropdown-toggle::after { - display:none; -} -@media (min-width:768px) { - .searchbar-toggle { - display: none; - } -} -.program-header .navbar .navbar-collapse { - border: none; -} -@media (min-width: 768px) { - .program-header .navbar .navbar-collapse { - border-top: 1px solid #d1d5d8; - } -} -@media (min-width: 992px) { - .program-header .navbar .navbar-collapse { - border:none; - } - .program-header .navbar .navbar-collapse.xl-row { - border-top: 1px solid #d1d5d8; - } - .program-header .navbar .navbar-collapse.rows { - border-top: 1px solid #d1d5d8; - } -} -@media (min-width: 1200px) { - .program-header .navbar .navbar-collapse.xl-row { - border-top:none - } -} -.application .navbar-light .navbar-nav .dropdown-menu, .program-header .navbar-light .navbar-nav .dropdown-menu { - left:auto; - right:0; - white-space: nowrap; -} -.application .navbar-light .navbar-toggler, .program-header .navbar-light .navbar-toggler { - border: none; -} -.navbar-toggle-label { - font-size: 0.8em; - color: #fff; -} -#app-menu, #program-menu { - background-color: #5e6a71; - color:#fff; - width: 100%; - position: relative; -} -#app-menu, #program-menu:hover { - background-color: #0079c2; -} -@media (min-width:768px) { - #app-menu, #program-menu { - width:auto - } -} -#app-menu, #program-menu:before { - content:""; - background-color: #5e6a71; - position: absolute; - height: 100%; - width: 4000px; - left: -2000px; - z-index: -1; -} -#app-menu, #program-menu:hover:before { - background-color: #0079c2; -} -.custom-toggler .navbar-toggler-icon { - background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgb(255,255,255)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 8h24M4 16h24M4 24h24'/%3E%3C/svg%3E"); -} -.navbar-wrapper { - min-height: 50px; -} -.navbar-wrapper a { - text-decoration: none; -} -.navbar-wrapper.corporate-topnav .container { - border-top:none; -} -@media (min-width:768px) { - .navbar-wrapper .container { - border-top: 1px solid #d1d5d8; - } - .navbar-toggler { - display: none; - } -} -.navbar-light .navbar-nav > li > a { - border-top:3px solid transparent; - color: #5e6a71; - padding-top: 10px; - padding-bottom: 10px; - line-height:30px; -} -@media (min-width:768px) { - .navbar-light .navbar-nav > li > a { - text-transform: none; - border-top:3px solid transparent; - background-color: transparent; - } -} -.application .navbar-light .navbar-nav > li > a, .program-header .navbar-light .navbar-nav > li > a { - padding-top: 15px; - padding-bottom: 15px; - border-top:3px solid transparent; -} -.application .navbar-light .navbar-nav > li > a:hover, .application .navbar-light .navbar-nav > li > a:focus, .application .navbar-light .navbar-nav > li.active > a:hover, .application .navbar-light .navbar-nav > li.active > a:focus, .program-header .navbar-light .navbar-nav > li > a:hover, .program-header .navbar-light .navbar-nav > li > a:focus, .program-header .navbar-light .navbar-nav > li.active > a:hover, .program-header .navbar-light .navbar-nav > li.active > a:focus { - background-color: #0B5E90; - border-top: 3px solid transparent; - color: #fff; -} -@media (min-width:768px) { - .application .navbar-light .navbar-nav > li.active > a, .application .navbar-light .navbar-nav > li > a:hover, .application .navbar-light .navbar-nav > li > a:focus, .application .navbar-light .navbar-nav > li.active > a:hover, .application .navbar-light .navbar-nav > li.active > a:focus, .program-header .navbar-light .navbar-nav > li.active > a, .program-header .navbar-light .navbar-nav > li > a:hover, .program-header .navbar-light .navbar-nav > li > a:focus, .program-header .navbar-light .navbar-nav > li.active > a:hover, .program-header .navbar-light .navbar-nav > li.active > a:focus { - background-color: #5E6A71; - } -} -.navbar-light .navbar-nav > li > a .fa-angle-down:before { - content: "\f107"; -} -.navbar-light .navbar-nav > li.show > a .fa-angle-down:before { - content: "\f106"; -} -@media(min-width:768px) { - .application .navbar-light .navbar-nav > li.show > a, .program-header .navbar-light .navbar-nav > li.show > a { - border-top:3px solid transparent; - } -} -.application .navbar-light .navbar-nav > li.show > a:hover, .application .navbar-light .navbar-nav > li.show > a:focus, .program-header .navbar-light .navbar-nav > li.show > a:hover, .program-header .navbar-light .navbar-nav > li.show > a:focus { - background-color: #0B5E90; - color: #fff; -} -@media(min-width:768px) { - .application .navbar-light .navbar-nav > li.show > a:hover, .application .navbar-light .navbar-nav > li.show > a:focus, .program-header .navbar-light .navbar-nav > li.show > a:hover, .program-header .navbar-light .navbar-nav > li.show > a:focus { - background-color: #5E6A71; - } -} -.application .navbar-light .navbar-nav .show .dropdown-menu > li > a, .program-header .navbar-light .navbar-nav .show .dropdown-menu > li > a { - background-color: #282e2e; - border-bottom: 1px solid #000; - color: #62d2ff; - padding-top:10px; - padding-bottom:10px; -} -@media(min-width:768px) { - .application .navbar-light .navbar-nav .show .dropdown-menu > li > a, .program-header .navbar-light .navbar-nav .show .dropdown-menu > li > a { - background-color: #5E6A71; - border-bottom: 1px solid #4B545A; - color: #fff; - } -} -.application .navbar-light .navbar-nav .show .dropdown-menu > li > a:hover, .application .navbar-light .navbar-nav .show .dropdown-menu > li > a:focus, .program-header .navbar-light .navbar-nav .show .dropdown-menu > li > a:hover, .program-header .navbar-light .navbar-nav .show .dropdown-menu > li > a:focus { - background-color: #0B5E90; - color: #fff; -} -.application .navbar-light .navbar-nav .show .dropdown-menu > li > a .fa-home, .program-header .navbar-light .navbar-nav .show .dropdown-menu > li > a .fa-home { - position: absolute; - right:10px; - top:15px; -} -.application .navbar-light .navbar-nav .show .dropdown-menu > li > a, .program-header .navbar-light .navbar-nav .show .dropdown-menu > li > a { - position: relative; -} -.application .navbar-light .navbar-nav .dropdown > ul ul, .program-header .navbar-light .navbar-nav .dropdown > ul ul { - padding-left:0; - list-style-type: none; -} -.application .navbar-light .navbar-nav .dropdown > ul ul li, .program-header .navbar-light .navbar-nav .dropdown > ul ul li { - background-color: #282e2e; -} -.application .navbar-light .navbar-nav .dropdown > ul ul a, .application .navbar-light .navbar-nav .dropdown > ul ul a:visited, .program-header .navbar-light .navbar-nav .dropdown > ul ul a, .program-header .navbar-light .navbar-nav .dropdown > ul ul a:visited { - color: #62d2ff; - display: block; - text-decoration: none; - padding-left:40px; - padding-top:10px; - padding-bottom:10px; -} -.application .navbar-light .navbar-nav .dropdown > ul ul a:hover, .program-header .navbar-light .navbar-nav .dropdown > ul ul a:hover { - background-color: #0B5E90; - color: #fff; -} -.navbar, .navbar-nav, .navbar-collapse { - border:0; - margin-bottom:0; - min-height: 0; -} -ul.navbar-nav { - margin-top:0; - margin-bottom: 0; -} -ul.dropdown-menu { - border:0; - padding-top: 0; - padding-bottom:0; - margin-top:0; -} -.corporate-topnav .container { - padding-left: 0; - padding-right: 0; -} -@media (min-width:768px) { - .corporate-topnav .container { - padding-left: 15px; - padding-right: 15px; - } -} -.corporate-topnav .navbar { - padding: 0.5rem 1rem 0 1rem; -} -.navbar-wrapper.corporate-topnav { - border-bottom: 1px solid #d1d5d8; -} -.corporate-topnav .navbar-inverse { - text-align: center; - background-color: transparent; -} -.corporate-topnav .navbar-expand .navbar-nav > li > a { - display: block; - font-size: 0.75em; - padding-left: 0.5em; - padding-right: 0.5em; - text-transform: none; -} -@media(min-width: 576px) { - .corporate-topnav .navbar-expand .navbar-nav > li > a { - font-size: 0.8em; - padding-left: 0.8em; - padding-right: 0.8em; - } -} -.corporate-topnav .navbar-expand .navbar-nav > li > a { - background-color: transparent; -} -.corporate-topnav .navbar-expand .navbar-nav > li > a:hover, .corporate-topnav .navbar-expand .navbar-nav > li > a:focus { - background-color:#0079c2; - color: #fff; -} -.navbar-light .navbar-nav.ml-auto .nav-link { - color: #0079C2; -} -.navbar-light .navbar-nav .nav-link { - color: #5e6a71; - background-color: #ededed; - padding: 15px; -} -.navbar-light .navbar-nav .active > .nav-link, .navbar-light .navbar-nav .nav-link.active, .navbar-light .navbar-nav .nav-link.show, .navbar-light .navbar-nav .show > .nav-link { - background-color:#0079c2; - color: #fff; -} -@media(min-width:992px) { - .navbar-wrapper.corporate-topnav { - margin-top:-17px; - } -} -.corporate-topnav .navbar-expand .navbar-nav > li.active > a, .corporate-topnav .navbar-expand .navbar-nav > li.active > a:hover { - background-color:#0079c2; -} -.corporate-topnav .navbar-expand .navbar-nav > li.open > a { - background-color: #0079c2; -} -.corporate-topnav .navbar-expand .navbar-nav .show .dropdown-menu > li > a { - background-color: #5E6A71; - border-bottom: 1px solid #4B545A; - display: block !important; - color: #fff; -} -@media(min-width:768px) { - .corporate-topnav .navbar-expand .navbar-nav .show .dropdown-menu > li > a { - background-color: #5E6A71; - border-bottom: 1px solid #4B545A; - color: #fff; - } -} -.corporate-topnav .navbar-expand .navbar-nav .show .dropdown-menu > li > a:hover, .corporate-topnav .navbar-expand .navbar-nav .show .dropdown-menu > li > a:focus { - background-color: #0B5E90; - color: #fff; -} -@media (min-width:768px) { - .corporate-topnav .navbar-expand .navbar-nav > li > a { - font-size: 1em; - } -} -.corporate-topnav .navbar-expand .navbar-nav li.dropdown { - position: static; -} -.corporate-topnav .navbar-expand .navbar-nav .show .dropdown-menu { - background-color: #5E6A71; - border-top:0; - font-size: 1em; - width: 100%; -} -@media (min-width:768px) { - .corporate-topnav .navbar-expand .navbar-nav .show .dropdown-menu { - display:block; - left:auto; - width:auto; - } -} -.banner-logo { - width: 100%; - border-bottom: none; - min-height:60px; -} -header .logo { - background: url('client/img/nrel-logo-web.svg') no-repeat center; - height: 75px; - background-size: 190px; - margin: 0 0 10px 0; -} -.program-header { - background-color: transparent; -} -.program-header .navbar-inverse { - border-top: 1px solid #d1d5d8; -} -.program-header .container { - padding-right: 0; - padding-left: 0; -} -header.notopnav {border-bottom: 1px solid #d1d5d8; margin:0 0 1.5em 0;} -@media only screen and (min-width : 480px) { - header .logo { - height: 85px; - background-size: 214px; - margin: 0 0 10px 0; - } -} -@media (min-width: 768px) { - header .logo { - background: url('client/img/nrel-logo-web.svg') no-repeat left; - height:130px; - background-size: 328px; - } - .program-header .container { - padding-right: 15px; - padding-left: 15px; - } - .banner-logo { - min-height:92px; - } - .program-header { - background-color: #ededed; - } -} -@media only screen and (min-width : 1200px) { - .program-header .navbar-inverse { - border-top: none; - } -} -.searchbar-toggle { - border: 1px solid #5e6a71 ; - border-radius:5px; - color: #5e6a71 ; - margin:15px; - padding: 8px 6px 8px 6px; - position: absolute; - right: 0; - top:0px; -} -@media (min-width: 768px) { - #searchbar-collapse { - background-color: transparent; - margin-left: 0px; - margin-right: 0px; - } - #searchbar-collapse:before { - background-color:transparent; - } -} -@media (min-width: 768px) { - #searchbar-collapse:after { - display: none; - } -} -.searchbar-form { - padding-top: 7px; - padding-bottom: 7px; -} -@media (min-width: 768px) { - .searchbar-form { - background-color: transparent; - float:right; - } -} -.searchbar-form button { - background-color: #D1D5D8; - border-color: #D1D5D8; - color: #000; - height:40px; - text-transform: uppercase; -} -@media (min-width: 768px) { - header .navbar-collapse, header .navbar-form { - padding-left:0; - padding-right: 0; - } -} -header nav.navbar { - border: none; - box-shadow: none; - text-align: center; -} -.searchbar-form .form-group { - display: inline-block; - margin-bottom: 0; - vertical-align: middle; -} -.searchbar-form .form-control { - display: inline-block; - width: 200px; - vertical-align: middle; - box-shadow: none; - height:40px; -} -@media (max-width: 350px) { - .searchbar-form .form-control { - width: 160px; - } -} -#page-tools { - margin: 2em 0; -} -footer { - background-color: #F5F5F5; - color: #000; - font-size: 14px; - font-weight: 400; - line-height: 1.2; - padding-bottom: 5em; -} -footer .footertop { - background-color: #D1D5D8; - padding: 2em 0; -} -footer .global div {margin: 1em 0 0 0; line-height:1.3;} -@media (min-width: 992px) { - footer .global div {margin: 0 2em;} - footer .global div:first-child {margin: 0 2em 0 0;} - footer .global div:last-child {margin: 0 0 0 2em;} -} -footer .footerbottom { - padding-top:2em; -} -footer .globalsecondary div, footer .nrel-attr {font-size: 0.75rem;} -footer .nrel-attr a:link, footer .nrel-attr a:visited {text-decoration:underline;} -footer .nrel-attr a:hover, footer .nrel-attr a:active {color: #0071b8;} -footer .header { - border-bottom:1px solid #5a5b5b; - font-size: 14px; - margin-top: 1em; - text-transform: uppercase; -} -footer .only-nrel { - font-size: 10px; -} -footer .social-links { - font-size: 14px; - margin:0 0 1em 0; -} -footer .social-links li { - padding: 0; -} -footer a, footer a:visited { - color: #000; - text-decoration: none; -} -footer a:hover, footer a:active { -color:#000; -text-decoration: underline; -} -footer .logo { - display: block; - max-width:100px; - margin-top: 1em; -} -footer ul { - list-style-type: none; - margin-left:0; - margin-bottom: 0; - padding-left: 0; -} -footer ul li { - line-height: 1.25em; - margin-bottom: 0.55em; -} -footer ul.dotlist li:before { - content: "\00a0 \00b7 \00a0"; -} -footer ul.dotlist li:first-child:before { - content: ""; -} -@media(min-width:768px) { - footer .ft-border-right { - border-right: 1px solid #5a5b5b; - } - footer .social-links { - font-size: 14px; - margin:0; - } - footer .header { - margin-top: 0; - } -} -#perPage { - margin-left: 10px; -} -.results-header .form-inline input[type=text] { - width: 99%; -} -@media(min-width:768px){ - .results-header .form-inline [class^="col-"] { - padding-right:0; - } - .results-header .form-inline [class^="col-"]+[class^="col-"] { - padding-left: 0; - } -} -.results-header a { - text-decoration: none; -} -.results-header a:hover { - text-decoration: underline; -} -.results-header p, .results-header label { - font-size: 18px; -} -.results-header .results-notes { - margin: 1.5em 0; -} -.results-header .results-notes p { - margin:0; -} -.results-header .results-total p { - margin-bottom: 0; -} -.results-header .highlighted { - color: #c60; - font-style: italic; - font-weight: bold; -} -.results-list { - margin-top:1.5em; - padding-top:1.5em; - border-top: 1px solid #ccc; -} -.results-highlighted { - font-weight: bold; -} -.results-item { - margin-bottom: 1.5em; - padding-bottom: 1.5em; - border-bottom: 1px solid #ccc; - text-decoration: none; -} -.results-item .summary { - color: initial; -} -.results-item .footer { - color:#5E6A71; -} -.results-pager .pagination { - margin: 0; -} -.results-pager p { - padding-top:.5em; -} -.results-pager a { - text-decoration: none; -} -.results-pager a:hover { - text-decoration: underline; -} -.results-pager .glyphicon { - font-size: 75%; -} -figure { - display: table; - margin:1em auto; -} -figure img { - display: block; -} -figcaption { - display: table-caption; - caption-side: bottom; - font-size: 12px; - color: #757575; - margin-top: .5em; -} -.hpf6df8a6f-3fb6-4eba-b28d-c99912998305{ - display:none; - margin-left:-1000px; -} -.hpe2cd04f6-da55-4773-80a2-c25c4ca203c4{ - display:none; - margin-left:-1000px; -} -.content-list-widget { - font-weight: 400; - font-size: 1.1em; - line-height: 1.2; - margin-bottom: 3em; -} -.content-list-widget .header-box { - background-color: #ededed; - border-bottom: 3px solid #00b5ef; -} -.content-list-widget .header-box .title { - color: #000; - font-size: 1.35em; - margin-bottom: 0; - padding: .8em; - text-transform: none; -} -.content-list-widget .header-box .title span { - font-weight: 500; - text-transform:uppercase; - color:#0079c5; -} -.content-list-widget .content-box { - border: 1px solid #D1D5D8; - padding: 1em; - margin: 0; -} -.content-list-widget .list { - padding-left: 1.35em; -} -.content-list-widget .title+.list { - border-top: 1px solid #D1D5D8; - margin-top: .5em; - padding-top: .75em; -} -.content-list-widget .list, .content-list-widget .btn-link { - text-decoration: none; - text-transform: none; -} -.content-list-widget .btn-link:visited { - color:#0079c2; -} -.content-list-widget .text-left .btn-link { - margin-left: 0; - padding-left: 0; -} -.content-list-widget .list .headline, .content-list-widget .list .date { - margin-bottom: 0; -} -.content-list-widget .footnotes{ - border-top: 1px solid #D1D5D8; - padding-top: 1em; -} -.content-list-widget .text-right { - text-align: inherit; -} -@media (min-width:768px) { - .content-list-widget .text-right { - text-align: right; - } - .addthis_inline_share_toolbox_hq4v {margin-left:0px;} -} -.bg-blue { - background-color:#0079C2; - border-bottom:none; -} -.blogpost h2 {margin:0.5em 0;} -.addthis_inline_share_toolbox_hq4v {margin-left:-10px;} -blockquote {color: #757575; border-top: 5px solid #00A4E4; border-bottom: 5px solid #00A4E4; padding: 1em 0;} -blockquote.green {color: #757575; border-top: 5px solid #8cc63f; border-bottom: 5px solid #8cc63f; padding: 1em 0;} \ No newline at end of file diff --git a/frontend/client/css/nrel.complete.min.css b/frontend/client/css/nrel.complete.min.css deleted file mode 100755 index 5e1ba75..0000000 --- a/frontend/client/css/nrel.complete.min.css +++ /dev/null @@ -1 +0,0 @@ -@charset "UTF-8";body, html {overflow-x:hidden;}body {background-color: transparent;color: #222;font-family:'Roboto', 'Helvetica Neue', Helvetica, sans-serif;font-weight: 400;line-height: 1.65;padding: 0;}h1 {color:#222;font-weight: 300;font-size: 2em;margin-top: 0;}@media (min-width: 768px) {h1 {font-size: 2.15em;}}h2 {color: #e07700;font-weight: 300;font-size: 1.75em;}h3 {color:#222;font-weight: 400;font-size: 1.5em;}h4 {color: #222;font-size: 1.375em;font-weight: 400;}h5 {color:#222;font-weight: 400;font-size: 1.25em;}h6 {color:#222;font-weight: 400;font-size: 1.125em;}a {color: #0071b8;}a:visited {color: #9650b9;}a:hover, a:active {color: #0071b8;text-decoration: underline;}.blue a:visited {color: #0071b8;}.green a:visited {color: #5D9732;}a.tile {display: block;}a.tile:hover {text-decoration: none;}a.fa {text-decoration: none;}a.fa:hover {text-decoration: underline;}h1 a[href]::after, h2 a[href]::after, h3 a[href]::after, h4 a[href]::after, h5 a[href]::after, h6 a[href]::after {font-family: FontAwesome;content: "\00a0\f054";display: inline;font-size: 0.65em;}a[href]::after {font-family: FontAwesome;font-size: 0.85em;}h1 a[href$=".pdf"]::after, h2 a[href$=".pdf"]::after, h3 a[href$=".pdf"]::after, h4 a[href$=".pdf"]::after, h5 a[href$=".pdf"]::after, h6 a[href$=".pdf"]::after, a[href$=".pdf"]::after {content: "\00a0\f1c1";display: inline;}h1 a[href$=".doc"]::after, h2 a[href$=".doc"]::after, h3 a[href$=".doc"]::after, h4 a[href$=".doc"]::after, h5 a[href$=".doc"]::after, h6 a[href$=".doc"]::after, a[href$=".doc"]::after {content: "\00a0\f1c2";display: inline;}h1 a[href$=".xls"]::after, h2 a[href$=".xls"]::after, h3 a[href$=".xls"]::after, h4 a[href$=".xls"]::after, h5 a[href$=".xls"]::after, h6 a[href$=".xls"]::after, a[href$=".xls"]::after{content: "\00a0\f1c3";display: inline;}h1 a[href$=".ppt"]::after, h2 a[href$=".ppt"]::after, h3 a[href$=".ppt"]::after, h4 a[href$=".ppt"]::after, h5 a[href$=".ppt"]::after, h6 a[href$=".ppt"]::after, a[href$=".ppt"]::after {content: "\00a0\f1c4";display: inline;}h1 a[href$=".jpg"]::after, h2 a[href$=".jpg"]::after, h3 a[href$=".jpg"]::after, h4 a[href$=".jpg"]::after, h5 a[href$=".jpg"]::after, h6 a[href$=".jpg"]::after, h1 a[href$=".png"]::after, h2 a[href$=".png"]::after, h3 a[href$=".png"]::after, h4 a[href$=".png"]::after, h5 a[href$=".png"]::after, h6 a[href$=".png"]::after, h1 a[href$=".gif"]::after, h2 a[href$=".gif"]::after, h3 a[href$=".gif"]::after, h4 a[href$=".gif"]::after, h5 a[href$=".gif"]::after, h6 a[href$=".gif"]::after, a[href$=".jpg"]::after, a[href$=".png"]::after, a[href$=".gif"]::after {content: "\00a0\f1c5";display: inline;}h1 a[href$=".mp4"]::after, h2 a[href$=".mp4"]::after, h3 a[href$=".mp4"]::after, h4 a[href$=".mp4"]::after, h5 a[href$=".mp4"]::after, h6 a[href$=".mp4"]::after, a[href$=".mp4"]::after {content: "\00a0\f03d";display: inline;}h1 a[href$=".txt"]::after, h2 a[href$=".txt"]::after, h3 a[href$=".txt"]::after, h4 a[href$=".txt"]::after, h5 a[href$=".txt"]::after, h6 a[href$=".txt"]::after, a[href$=".txt"]::after {content: "\00a0\f15b";display: inline;}h1 a[href$=".zip"]::after, h2 a[href$=".zip"]::after, h3 a[href$=".zip"]::after, h4 a[href$=".zip"]::after, h5 a[href$=".zip"]::after, h6 a[href$=".zip"]::after, a[href$=".zip"]::after {content: "\00a0\f1c6";display: inline;}img a[href]::after, img a[href$=".pdf"]::after, img a[href$=".doc"]::after, img a[href$=".xls"]::after, img a[href$=".ppt"]::after, img a[href$=".jpg"]::after, img a[href$=".png"]::after, img a[href$=".gif"]::after, img a[href$=".mp4"]::after, img a[href$=".txt"]::after, img a[href$=".zip"]::after {content:none;}a.noicon::after {content:none;}#content {padding-bottom: 5em;}@media (min-width: 768px) {body.layout-12 > #page-tools .row > .col-sm-9 {width: 100%;}body.layout-12 > #page-tools .row > .col-sm-offset-3 {margin-left:0;}}.list-headline {border-bottom: 1px solid #000;padding-bottom: 10px;}.lead {font-weight: 300;font-size: 1.15em;line-height: 1.4em;}.banner-site {font-size: 1.5rem;line-height: 1.2em;font-weight: 400;margin: 0 0 .3em;text-align: center;text-decoration: none;}@media (min-width: 768px) {.banner-site {font-size: 2rem;font-weight: 300;text-align: left;margin: 5px 0;}}.banner-site a, .banner-site a:hover {color: #000;text-decoration: none;}.banner-site a:visited {color: #4B545A;text-decoration: none;}.navbar-light .navbar-brand {color:#4B545A;}.breadcrumb {background-color: transparent;font-size: 14px;margin-bottom: 1em;padding-left:0;}.breadcrumb > li + li:before {content: "\00BB";padding: 0 5px;color: #000;}.breadcrumb a, .breadcrumb a:visited {color: #0071b8;text-decoration: none;}nav.breadcrumb.invisible {margin-top: -50px;}.hide {display: none !important;}.sidenav {margin: 2em 0;}.sidenav ul.nav li {font-weight: 400;line-height: 1.3;width:100%;}.sidenav a, .sidenav a:visited {color:#0079C2;text-decoration: none;}.nav>li>a:hover, .nav>li>a:focus {background-color: transparent;}@media (min-width: 992px) {.sidenav {margin: 0.6em 0;}}.sidenav > ul.nav > li:first-child > a {border-top: 3px solid #D1D5D8;}.sidenav > ul.nav > li > a {background-color: transparent;border-bottom:1px solid #d1d5d8;border-top: 0;}.sidenav > ul.nav > li > a:hover, .sidenav > ul.nav > li > a:focus, .sidenav > ul.nav > li.active > a, .sidenav > ul.nav > li.active > a:visited {background-color: #0079c2;color: #fff;}.sidenav > ul.nav ul.nav li > a {padding-left: 30px;border-bottom: 1px dashed #d1d5d8;}.sidenav > ul.nav ul.nav li:last-child > a {border-bottom: 1px solid #d1d5d8;}.sidenav > ul.nav ul.nav li > a:hover, .sidenav > ul.nav ul.nav li > a:focus, .sidenav > ul.nav ul.nav li.active > a, .sidenav > ul.nav ul.nav li.active > a:visited {background-color: #E5F1F9;color: #0064A2;}.sidenav ul.nav ul.nav ul.nav li {color: #0079c2;display: list-item;}.sidenav ul.nav ul.nav ul.nav li > a:before {content: '\25A0\00a0';vertical-align: text-bottom;}.sidenav ul.nav ul.nav ul.nav li > a {border-bottom: none;margin-left: 1em;text-indent: -0.85em;}.sidenav ul.nav ul.nav ul.nav li:last-child > a {border-bottom: 1px dashed #d1d5d8;}.sidenav ul.nav ul.nav ul.nav li:hover, .sidenav ul.nav ul.nav ul.nav li:focus, .sidenav ul.nav ul.nav ul.nav li.active {background-color: #E5F1F9;color: #0064A2;}h1.green, h2.green, h3.green, h4.green, h5.green, h6.green, p.green {color: #5D9732;}div.green {background-color: #5D9732;}h1.grey, h1.gray, h2.grey, h2.gray, h3.grey, h3.gray, h4.grey, h4.gray, h5.grey, h5.gray, h6.grey, h6.gray, p.grey, p.gray {color: #5E6A71;}div.grey, div.gray {background-color: #5E6A71;color: #fff;}h1.blue, h2.blue, h3.blue, h4.blue, h5.blue, h6.blue, p.blue {color: #0079c2;}div.blue {background-color: #0079C2;color: #fff;}h1.black, h2.black, h3.black, h4.black, h5.black, h6.black, p.black {color: #000;}div.black {background-color: #000;color: #fff;}h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {text-decoration: none;}.allcaps {text-transform: uppercase;}.nomargin + h2, .nomargin + h3, .nomargin + h4, .nomargin + h5, .nomargin + h6 {margin-top: 0;}.smaller {font-size:90%;}.card {display:block;}.bg-blue .card-title {color:#fff;}.headerlink-cr::after {font-family: FontAwesome;content: "\00a0\f054";display: inline;font-size: 0.65em;}.card-header {background-color: #fff;border-bottom: 6px solid #E66D0A;}.card-header h2 {font-size: 1.5em;font-weight: 400;margin:0;}.card-header h2 a:link, .card-header h2 a:visited, .card-header h3 a:link, .card-header h3 a:visited {color:#212121;}.card-header h2 a:hover, .card-header h2 a:focus, .card-header h3 a:hover, .card-header h3 a:focus {color:#cc6600;}.card-header.orange {background-color: #fff;border-bottom: 6px solid #E66D0A;}.card-text {line-height: 1.35em;}.card-title {margin-bottom:0;}.card-footer {background-color: #fff;}.fw-feat {background-color: #fff;background-clip: border-box;border: 1px solid rgba(0,0,0,.125);border-radius: .25rem;width:100%;}.fw-feat h3 a:link, .fw-feat h2 a:link, .fw-feat h3 a:visited, .fw-feat h2 a:visited {color:#000;text-decoration: none;}.fw-feat h3 a:hover, .fw-feat h2 a:hover, .fw-feat h3 a:active, .fw-feat h2 a:active {color:#e07700;text-decoration: none;}.fw-feat-info {padding:1em 1em 0 1em;border-bottom: 6px solid #E66D0A;border-top:none;}@media only screen and (min-width : 1200px) {.overlay-text {top: 65px;}.fw-feat-info {border-bottom:none;border-top: 6px solid #E66D0A;}}.card.bg-secondary .card-body {border-top: 6px solid #e07700;}.card.bg-secondary .card-body h2, .card.bg-secondary .card-body h3, .card.bg-secondary .card-body h4 {color:#F5F5F5;}.orange-card .card-body {border-top: 6px solid #e07700;font-size: 1rem;}.orange-card h3 {font-weight:400;}.orange-card h4 a:link, .orange-card h4 a:visited {color:#e07700;font-weight:300;}.orange-card h4 a:hover, .orange-card h4 a:active {color:#0071b8;font-weight:300;}.img-feat-orange {background: rgba(255, 255, 255, 0.8);border-bottom: 6px solid #E66D0A;position: absolute;bottom: 0;left: 0;padding: 1em;margin:0;width: 100%;}.img-feat-orange a:link, .img-feat-orange a:visited {color:#000;}.img-feat-orange a:active, .img-feat-orange a:hover {color:#e07700;}.border-danger {border-color:#D9531E !important;}.border-danger .card-header {background-color: #D9531E;border-bottom:none;}.border-primary {border-color:#0079C2 !important;}.border-primary .card-header {background-color: #0079C2;border-bottom:none;}.border-success {border-color:#5D9732 !important;}.border-success .card-header {background-color: #5D9732;border-bottom:none;}.border-warning {border-color:#F7A11A !important;}.border-warning .card-header {background-color: #F7A11A;border-bottom:none;}.border-danger .card-header h3, .border-primary .card-header h3, .border-success .card-header h3, .border-warning .card-header h3 {color:#fff;}.card-title img {width: 30px;margin-bottom:5px;margin-right: 6px;}.visionbox.main {background-color:#0079C2;color:#fff;}.visionbox.ceem {background-color:#FFC432;color:#3A4246;}.visionbox.e2m {background-color:#933C06;color:#fff;}.visionbox.iep {background-color:#5D9732;color:#fff;}.visiontext {padding:1em;}.visionlink {font-weight: 300;font-size:1.35em;line-height:1.1em;}.main .visionlink a:link, .main .visionlink a:visited, .e2m .visionlink a:link, .e2m .visionlink a:visited, .iep .visionlink a:link, .iep .visionlink a:visited {color:#fff;}.ceem .visionlink a:link, .ceem .visionlink a:visited {color:#3A4246;}.visionicon {height:90px;margin:0.35em auto;}.vision h3 a:link, .vision h3 a:visited {color:#333;}.vision h3 a:hover, .vision h3 a:active {color:#333;text-decoration:underline;}.iep-top {background-color:#8CC63F;text-align:center;}.iep-text {border:1px solid #8CC63F;padding:1em;}.e2m-top {background-color:#D9531E;text-align:center;}.e2m-text {border:1px solid #D9531E;padding:1em;}.ceem-top {background-color:#F7A11A;text-align:center;}.ceem-text {border:1px solid #F7A11A;padding:1em;}.nrelvision-top {background-color:#E9ECEF;text-align:center;}.nrelvision-text {border:1px solid #E9ECEF;padding:1em;}@media (max-width: 768px) {.card-deck-wrapper {margin-right:0;margin-left:0;}.card-deck {display: block;width: 100%;margin-bottom: .75rem;table-layout: fixed;border-spacing: 1.25rem 0;}.card-deck .card {display: block;margin-bottom: .75rem;vertical-align: top;}}#accordion .card, .accordion .card, [id^="accordion_"] .card, .accordion .card {border: 1px solid #0079C2;border-radius: 0;margin: -1px -1px 0.5rem -1px;}#accordion .card-header, .accordion .card-header, [id^="accordion_"] .card-header, .accordion .card-header {padding: 0;margin: 0;border-radius: 0;border-bottom: none;}#accordion .card-header .btn, .accordion .card-header .btn, [id^="accordion_"] .card-header .btn, .accordion .card-header .btn {text-transform: none;font-size: 1.3rem;font-weight: 300;width:100%;text-align: left;border-radius: 0;line-height: 1;padding: 0.65rem 0.65rem 0.65rem 40px;}#accordion .card-header .btn-link:hover, [id^="accordion_"] .card-header .btn-link:hover, #accordion .card-header .btn-link:active, [id^="accordion_"] .card-header .btn-link:active, #accordion .card-header .btn-link:focus, [id^="accordion_"] .card-header .btn-link:focus, .accordion .card-header .btn-link:hover, .accordion .card-header .btn-link:active, .accordion .card-header .btn-link:focus {color: #0079C2;text-decoration: none;}#accordion .card-header .btn[aria-expanded="true"], [id^="accordion_"] .card-header .btn[aria-expanded="true"], .accordion .card-header .btn[aria-expanded="true"] {background-color: #0079C2;color: #fff;background-image: url('client/img/icon_minus.svg');background-repeat: no-repeat;background-position: 1% center;background-size: 25px 25px;margin-left: 0px;padding-left: 40px;display: block;}#accordion .card-header .btn[aria-expanded="false"], [id^="accordion_"] .card-header .btn[aria-expanded="false"], .accordion .card-header .btn[aria-expanded="false"] {background-color: #fff;color: #0079C2;background-image: url('client/img/icon_plus_blue.svg');background-repeat: no-repeat;background-position: 1% center;background-size: 25px 25px;margin-left: 0px;padding-left: 40px;display: block;}#accordion .card-header .btn:hover, #accordion .card-header .btn:focus, [id^="accordion_"] .card-header .btn:hover, [id^="accordion_"] .card-header .btn:focus {background-color: #0079C2;color: #fff;background-image: url('/_resources/images/icon_minus.svg');background-repeat: no-repeat;background-position: 1% center;background-size: 25px 25px;}.accordion > .card:not(:first-of-type):not(:last-of-type), .accordion > .card:first-of-type {border-bottom: 1px solid #0079C2;}.nrel-list .card-header h3 a:hover, .nrel-list .card-header h3 a:focus {color: #303030;text-decoration:underline;}.nrel-list .card-header span a:link, .nrel-list .card-header span a:visited {color:#212121;text-decoration:underline;font-size: 0.9em;}.nrel-list .card-header span a:hover, .nrel-list .card-header span a:focus {color:#F5F5F5;}ul.list-group>li:nth-child(odd){background-color:#F5F5F5;}.nrel-list .card-header {background-color: #80D0FF;border-bottom: 1px solid rgba(0,0,0,.125);}.nrel-list .card-header h3 {font-size: 1.35em;font-weight:400;margin:0;}.nrel-list .list-group-item .date {font-size: 0.85em;margin: 0.3em 0 0 0;font-weight: 300;}.nrel-list .list-group-item p, .list-group-item {font-weight: 400;line-height: 1.2;margin:0;}.feature-teaser {background-color: #F5F5F5;padding: 1.5em;height:100%;}.feature-teaser.ft-blue {background-color: #52BFFF;color:#333;}.feature-teaser h1 {font-size: 2em;}.feature-teaser h1 strong {font-weight: bold;}.feature-teaser p.lead {font-size: 1rem;}.feature-teaser.ft-blue a:link, .feature-teaser.ft-blue a:visited {color: #000;}@media (min-width: 768px) {.feature-teaser h1{font-size: 3em;}.feature-teaser p.lead {font-size: 1.25rem;}}@media (min-width: 992px) {.feature-teaser h1{font-size: 2em;}.feature-teaser p.lead {font-size: 1rem;}}@media (min-width: 1200px) {.feature-teaser {padding: 2em;}.feature-teaser h1 {font-size: 2.5rem;}.feature-teaser p.lead {font-size: 1.25rem;}}.big-quote::before {color: #ccc;font-family: serif;font-size: 3em;line-height: 0;vertical-align: text-bottom;content:"\201C";margin-right:3px;float: left;margin-top: -25px;padding-top: 45px;}.header-description {color:#757575;font-size: 1rem;font-weight: 400;margin-top: 0;}h2 .fa, h3 .fa, h4 .fa, h5 .fa, h6 .fa, .header .fa {font-size: 80%;vertical-align:middle;}.headerlink:after {font-family: FontAwesome;content: "\00a0\f054";display: inline;font-size: 0.65em;}.more, .learn-more {font-weight: 400;}.more a, .more a:visited, .learn-more a, .learn-more a:visited {color:#0071b8;text-decoration: none;}.more a:hover, .learn-more a:hover {text-decoration: underline;}.nav-tabs {margin-top:1em;}.nav-tabs {font-size: 21px;font-weight: 300;}.nav-tabs .nav-link {margin-bottom: -2px;border-bottom: 1px solid #fff;border-radius: 7px 7px 0 0;margin-right:0;background-color: #fff;border-color: #e9ecef;}.nav-tabs .nav-link.active, .nav-tabs .nav-link:hover {border-color: #dee2e6 #dee2e6 #fff;}.nav-tabs .nav-item {margin-bottom: 0;}.tab-pane {padding:2em 1em;}.panel {-webkit-box-shadow: none;box-shadow: none;}.panel-default {border: 1px solid #127BBF;}.panel-body {padding: 15px;}.collapse.in {display: block;}.panel-heading > .panel-title > .accordion-toggle {margin-left:0px;padding-left:40px;display: block;}.panel-heading > .panel-title > .accordion-toggle, .panel-heading > .panel-title > .accordion-toggle:visited {color:#fff;}.panel-heading > .panel-title > .accordion-toggle {background-image: url('client/img/icon_minus.svg');background-repeat: no-repeat;background-position: 1% center;background-size: 25px 25px;}.panel-heading > .panel-title > .accordion-toggle.collapsed {background-image: url('client/img/icon_plus_blue.svg');}.panel-group .panel {margin-bottom: 12px;}.panel-group .panel + .panel {margin-top:1px;}.panel-group .panel, .panel-group .panel-heading {border-radius: 1px;}.panel-group .panel:first-child {border-top-left-radius: 4px;border-top-right-radius: 4px;}.panel-group .panel:last-child {border-bottom-left-radius: 4px;border-bottom-right-radius: 4px;}.panel-heading {padding: 0;cursor: pointer;}.panel-heading h4 {margin: 0;}h4.panel-title a[href]::after {content:"";}.panel-default > .panel-heading {background-color: transparent;color:#fff;}.panel-default > .panel-heading a {display: block;padding: 10px 15px;text-decoration: none;}.panel-default > .panel-heading a:hover {text-decoration: none;color:#fff;}.panel-group.inverse .panel-heading > .panel-title > a.accordion-toggle, .panel-group.inverse .panel-heading > .panel-title > a.accordion-toggle:visited {background-color: #127BBF;color:#fff;}.panel-group.inverse .panel-heading > .panel-title > a.accordion-toggle.collapsed, .panel-group.inverse .panel-heading > .panel-title > a.accordion-toggle.collapsed:visited {background-color: transparent;color: #127BBF;}.content-sandwich:before, .content-sandwich:after {content: " ";display: table;}.content-sandwich:after {clear:both;}.content-sandwich {border-top: 5px solid #ccc;border-bottom: 5px solid #ccc;margin-top: 1.65em;margin-bottom: 1.65em;padding: 1em 0;}.content-sandwich-topper + .content-sandwich {margin-top: 0;}.content-sandwich p {font-size: 16px;font-weight: normal;}.content-sandwich .headline, .content-sandwich .header {font-size: 18px;font-weight: 600;margin-bottom: 0;margin-top: 0;}.content-sandwich .headline ~ p, .content-sandwich .header ~ p, .content-sandwich h1 ~ p, .content-sandwich h2 ~ p, .content-sandwich h3 ~ p, .content-sandwich h4 ~ p, .content-sandwich h5 ~ p, .content-sandwich h6 ~ p {margin-top: 0;}.content-sandwich .item {border-bottom: 1px solid #ccc;padding: 1em 0;}.content-sandwich .item:last-child {border-bottom: none;}.content-sandwich .item p {margin-bottom: 0;}.content-sandwich ul {font-size: 16px;font-weight: normal;}.content-sandwich hr {border-top: 1px solid #ccc;}.content-sandwich img {margin: 0 auto;}.content-sandwich.vertical-aligned {}.content-sandwich.vertical-aligned [class*="col-"] .icon, .content-sandwich.vertical-aligned [class*="col-"] > *:only-child {margin-top: 1.5em;}@media(min-width:768px) {.content-sandwich.vertical-aligned [class*="col-"] .icon, .content-sandwich.vertical-aligned [class*="col-"] > *:only-child {margin-top: .75em;}}@media(min-width:1200px) {.content-sandwich.vertical-aligned [class*="col-"] .icon, .content-sandwich.vertical-aligned [class*="col-"] > *:only-child {margin-top: 1.5em;}}.content-sandwich.quicklinks .icon {margin-top: 1em;}.content-sandwich.quicklinks .header {margin-top: 1em;}@media (min-width: 992px) {.content-sandwich.quicklinks .header {margin-top: 1.65em;}}.bio {padding-bottom: 2em;}.bio:last-child {border-bottom: none;}.bio h3.header-description, .bio h4.header-description, .bio p.header-description {font-size: 1rem;color:#757575;font-weight:400;margin: 0 0 0.25em 0;}.bio p.header {font-size:1.3em;font-weight:300;line-height: 1.2;margin:0 0 0.25em 0;}.bio p.header a[href]::after {font-family: FontAwesome;content: "\00a0\f054";display: inline;font-size: 0.65em;}#staff_wrapper input {margin-bottom:0.5em;}#staff .bg-blue {color: #fff;background-color: #0079C2;}table#staff td {text-align: left;}table#staff td.child ul {padding-left: 1.2em;}table#staff td.child ul li {text-align: left;list-style: none;margin-left: 0;}.feature {border-bottom: 1px solid #000;display: block;margin-bottom:30px;min-height: 250px;text-decoration: none;}@media(min-width:768px){.feature {margin-bottom:12px;}}.row .col-sm-6:last-child .feature.last {border-bottom: none;}@media(min-width:768px){.feature.last {border-bottom: none;}}@media (min-width: 992px){.feature {min-height: 260px;}}@media (min-width: 1200px){.feature {min-height: 290px;}}.feature:hover {text-decoration: none;}.feature:hover .headline {color:#e07700;}.feature .category {color: #5E6A71;font-size: 16px;font-weight: 700;margin-bottom: 5px;margin-top: 10px;text-transform: uppercase;}.feature .headline {color: #0079C2;font-weight: 400;font-size: 1.4em;line-height: 1.2;margin-top:0;}.feature-secondary .link-tile {margin-top: 24px;}.feature-secondary .link-tile + .link-tile {margin-top:24px;}.showcase {opacity: 1;padding-bottom: 2em;padding-top: 2em;}.showcase .caption {background-color: rgba(0,0,0,.8);box-sizing: border-box;color: #fff;font-weight: 300;line-height: 1.1;padding: .5em 1em;position:relative;}.showcase .link-tile, .showcase .link-tile {position: relative;}.showcase .feature-primary .caption, .showcase .feature-secondary .caption {font-size: 30px;}.modal-footer {text-align: left;}@media(min-width:768px){.showcase {height:431px;}.showcase .caption {left: 0;right: 0;bottom: 0;position: absolute;}.feature-secondary .link-tile {margin-top: 0px;}.showcase .feature-primary .caption {font-size: 20px;}.showcase .feature-secondary .caption {font-size: 14px;}}@media(min-width:992px){.showcase {height:545px;}}@media(min-width:1200px){.showcase {height:649px;}.showcase .feature-primary .caption {font-size: 30px;}.showcase .feature-secondary .caption {font-size: 20px;}}.modal.fade .modal-dialog {transform: none !important;-webkit-transition: -webkit-transform 0.3s ease-out;-moz-transition: -moz-transform 0.3s ease-out;-o-transition: -o-transform 0.3s ease-out;transition: transform 0.3s ease-out;}.modal.in .modal-dialog {transform: none !important;}span.required {font-weight: normal;}select, select.form-control {background-image: linear-gradient(#FFF,#E6E6E6);box-shadow: inset 0 1px #FFF,inset 0 0 0 1px rgba(255, 255, 255, 0.5),0 1px 2px rgba(0, 0, 0, 0.1);}.dropdown-menu .divider {width: auto;border-top: none;margin: 9px auto;padding: 0;}a.btn, a.btn:visited, .btn a:hover {color: #fff;text-decoration: none;}a.btn-link, a.btn-link:visited {color: #0079C2;text-decoration: none;}.btn {font-weight: 400;min-height:40px;text-transform: uppercase;line-height: 1.8;}a.btn-default {color:#333;}.btn-default {color: #333;background-color: #fff;border-color: #ccc;}.btn-default:hover, .btn-default:focus, .btn-default:active, .btn-default.active, .open > .dropdown-toggle.btn-default {color: #333;background-color: #e6e6e6;border-color: #adadad;-webkit-box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.4);-moz-box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.4);box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.4);}.btn-primary {color: #fff;background-color: #0079c2;border-color: #0079c2;}.btn-primary:hover, .btn-primary:focus, .btn-primary:active, .btn-primary.active, .open > .dropdown-toggle.btn-primary {color: #b7d6e9;background-color: #3071a9;border-color: #285e8e;-webkit-box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.4);-moz-box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.4);box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.4);}.btn-success {color: #fff;background-color: #4c8224;border-color: #4c8224;}.btn-success:hover, .btn-success:focus, .btn-success:active, .btn-success.active, .open > .dropdown-toggle.btn-success {color: #d2e3c5;background-color: #3b651c;border-color: #3b651c;-webkit-box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.4);-moz-box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.4);box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.4);}.btn-info {color: #fff;background-color: #5e6a71;border-color: #5e6a71;}.btn-info:hover, .btn-info:focus, .btn-info:active, .btn-info.active, .open > .dropdown-toggle.btn-info {color: #cccccc;background-color: #3d4449;border-color: #3d4449;-webkit-box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.4);-moz-box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.4);box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.4);}.btn-warning {color: #000;background-color: #f7a11a;border-color: #f7a11a;}.btn-warning:hover, .btn-warning:focus, .btn-warning:active, .btn-warning.active, .open > .dropdown-toggle.btn-warning {color: #2d1c00;background-color: #b87813;border-color: #b87813;-webkit-box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.3);-moz-box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.3);box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.3);}.btn-danger {color: #fff;background-color: #933c06;border-color: #933c06;}.btn-danger:hover, .btn-danger:focus, .btn-danger:active, .btn-danger.active, .open > .dropdown-toggle.btn-danger {color: #dac4b7;background-color: #702e05;border-color: #702e05;-webkit-box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.4);-moz-box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.4);box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.4);}.btn-long {font-size:16px;height: 38px;line-height: 1.3em;white-space: normal;}.ou-form .btn {display:inline-block;padding:6px 12px;margin-bottom:0;font-weight:400;text-align:center;white-space:nowrap;vertical-align:middle;cursor:pointer;border:1px solid transparent;border-radius:4px;color:#fff;text-transform: uppercase;}.ou-form .btn-success:active,.ou-form .btn-success:hover {color: #d2e3c5;background-color: #3b651c;border-color: #3b651c;-webkit-box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.4);-moz-box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.4);box-shadow: inset 0px 2px 2px 0px rgba(50, 50, 50, 0.4);}@media only screen and (min-width : 992px) {.btn-long {height: 80px;}}@media only screen and (min-width : 1200px) {.btn-long {height: 60px;}}select.form-control, input.form-control {height: 40px;}@media(min-width:768px){.row-eq-height {display: -webkit-box;display: -webkit-flex;display: -ms-flexbox;display: flex;}.row-eq-height [class^="col-"] {min-height: 270px;}.row-eq-height .sink {bottom:0;left: 15px;right: 15px;position: absolute;}}.row-highlighted {background-color: #F5F5F5;margin-bottom:2em;margin-top:2em;padding-bottom:2em;padding-top:1em;}.marketing {text-align: center;}.marketing .img-circle {margin-top:2em;}.marketing h2 {padding-top:1em;}.marketing .btn {margin-bottom:2em;}iframe {border:0;}.video {margin: 1em 0;}.play-overlay {position: absolute;top: 50%;left: 50%;transform: translate(-50%, -50%);}.container-xs-height {display:table;padding-left:0px;padding-right:0px;}.row-xs-height {display:table-row;}.col-xs-height {display:table-cell;float:none;}@media (min-width: 768px) {.container-sm-height {display:table;padding-left:0px;padding-right:0px;}.row-sm-height {display:table-row;}.col-sm-height {display:table-cell;float:none;}}@media (min-width: 992px) {.container-md-height {display:table;padding-left:0px;padding-right:0px;}.row-md-height {display:table-row;}.col-md-height {display:table-cell;float:none;}}@media (min-width: 1200px) {.container-lg-height {display:table;padding-left:0px;padding-right:0px;}.row-lg-height {display:table-row;}.col-lg-height {display:table-cell;float:none;}}.col-top {vertical-align:top;}.col-middle {vertical-align:middle;}.col-bottom {vertical-align:bottom;}ul.fa-blue-arrow {list-style-type: none;}ul.fa-blue-arrow li {margin-bottom: 1em;}.fa-blue-arrow a {margin-left: -5px;color: #000;}.fa-blue-arrow a:visited {color: #000;}.fa-blue-arrow a::before {font-family: 'FontAwesome';content: '\f138';margin:0 5px 0 -15px;color: #0071b8;text-indent:15px;}.fa-blue-arrow a:hover {text-decoration: none;color: #0071b8;}ul.fa-check-circle:before {content: '';}ul.fa-check-circle {list-style-type: none;}ul.fa-check-circle li {margin-bottom: 1em;}ul.fa-check-circle li {margin-left: -5px;color:#000;}ul.fa-check-circle li:before {color: #0071b8;content: "\f058";font-family: 'FontAwesome';margin:0 5px 0 -15px;text-indent:15px;}.list-pipes li + li:before {content: " | ";}.list-pipes li {padding-left:0 !important;padding-right:0 !important;}.list-links.list-unstyled li {padding-bottom: 10px;}ul.fa-blue-arrow-text {list-style-type: none;}ul.fa-blue-arrow-text li {margin-bottom: 1em;}ul.padded-list li, ol.padded-list li {margin-bottom:1.75em;}.fa-blue-arrow-text a {margin-left: -17px;}.fa-blue-arrow-text a:before {font-family: 'FontAwesome';content: '\f138';margin:0 5px 0 -15px;color: #0071b8;text-indent:15px;display: inline-block;}.hero {margin-bottom: 1em;}figcaption, .caption {font-size: 12px;color: #757575;}.credit {font-style: italic;}a[data-toggle=lightbox] {text-decoration: none;}.enlarge {display: block;font-size: 12px;text-align: right;text-decoration: none;}.enlarge:before {content: '\f002';font-family: 'FontAwesome';padding-right: 3px;}.border {border:1px solid #666;}a.singleLightbox {text-decoration: none;}a.singleLightbox:visited {color: #0071b8;}a.singleLightbox:hover {color: #0071b8;text-decoration: underline;}.icon-circle {height: 100px;width: 100px;border-radius: 100px;background-color: #0071b8;line-height:100px;text-align: center;vertical-align: middle;color: #FFF;font-weight: 100;font-size: 2.2em;margin:0 auto;}@media (min-width: 768px) {.icon-circle {margin: auto;}}.icon-circle.green {background-color: #4c8224;}.divider {width: 12%;border-top: 1px solid #5D9732;margin: 0 auto;padding: 0 0 1.4em 0;}.cf:before, .cf:after {content: " ";display: table;}.cf:after {clear:both;}.clearboth {clear:both;}.wide-80 {min-width: 80%;}.wide-60 {min-width: 60%;}.wide-40 {min-width: 40%;}.wide-20 {min-width: 20%;}.nomargin {margin-top: 0;margin-bottom: 0;}.notop {margin-top:0;}.padtop {padding-top: 1em;}.padbottom {padding-bottom: 1em;}.clear {clear: both;}.kbd {padding: 2px 4px;font-size: 90%;color: #252525;background-color: #F5F5F5;border-radius: 3px;box-shadow: none;font-family: Menlo,Monaco,Consolas,"Courier New",monospace;}.pull-right {float: right;}.well {background-color: #F5F5F5;border-radius: 0;border:none;box-shadow: none;padding: 20px;margin-bottom: 20px;}.well.pull-right {margin-left:0;margin-bottom:1em;}.well h2 {color:#000;}@media (min-width: 768px) {.well.pull-right {margin-left:1em;}}.content-box {border: 1px solid #D1D5D8;padding: 1em;margin: 0;}.actionbox {border-radius: 0;border:none;box-shadow: none;margin-bottom:2em;margin-top: 2em;padding-bottom:1.5em;padding-top:1.5em;}.actionbox.pull-right {margin-top:0;}.actionbox > h2, .actionbox > h3, .actionbox .header {margin-top:0;}.actionbox .btn {margin-top:.5em;margin-bottom:.5em;}.actionbox.inline {padding-top:1em;padding-bottom:1em;}.actionbox.inline .header {margin-top:0;margin-bottom:.5em;}@media (min-width: 768px) {.actionbox.inline .header {display: inline;}}.actionbox.inline .btn {display: block;margin-bottom:0;margin-top:0;max-width: 320px;margin: 0 auto;}@media (min-width: 768px) {.actionbox.inline .btn {margin-left:2em;display: inline;vertical-align: top;line-height: 33px;}}.maintenancebox {border-radius: 0;border:none;box-shadow: none;margin-bottom:1.5em;margin-top: 1.5em;padding:1em;}.maintenancebox p {margin:0;text-align: center;}.graphic-box {position: relative;}.caption-box {background: #000;background-color: rgba(0, 0, 0, 0.8);box-sizing: border-box;color: #fff;padding:.5em;text-align: left;left: 0;right:0;bottom:0;width: 100%;position: relative;}@media (min-width:768px) {.caption-box {position: absolute;padding:1em;}}.caption-box .header, .caption-box h3 {font-weight: 300;}.caption-box .teaser {line-height: 34px;margin:0;}.caption-box .btn {text-transform: uppercase;}.caption-box a, .caption-box a:visited {color: #fff;}.caption-box .small {color: inherit;line-height: inherit;}.fileIcon {margin-left: 5px;}.lg-icon {width:6.5em;}.fs-icon, .md-icon {width:4.5em;}.sm-icon {width:3.5em;}.sm-icon-h {height:3.5em;}.icon-block:hover svg path, .icon-block:hover h4 a, .icon-block:hover {fill: #cc6600;color: #cc6600;text-decoration: none;}.icon-block h4 {margin-top:0.5em;}.v-center .row {display: table;margin: 0;table-layout: fixed;width: 100%;}.v-center [class^="col-"] {display: table-cell;vertical-align: middle;float: none;}.v-center [class^="col-"] *:first-child {margin-top: 0;}.v-center [class^="col-"] *:last-child {margin-bottom: 0;}.img-center {margin:0 auto;}.img-border{border: 1px solid #bebebe;}a > .fa:first-child {margin-right: 5px;padding-bottom: 3px;}a > .fa-ml {margin-left: 5px;margin-right:0!important;}.addthis_toolbar {text-align: center;}.addthis_toolbar a, .addthis_toolbar a:visited {color:#000;line-height: 26px;text-decoration: none;}#feedback-container {margin-top:3em;text-align: center;}#feedback-container .button-submit {float:none;}#feedback-container textarea {width: 50%;margin: 0 auto;}.addtocalendar var{display: none;}.addtocalendar {position: relative;display: inline-block;background: transparent!important;}.atcb-link {display: block;outline: none!important;cursor: pointer;}.atcb-link:focus~ul, .atcb-link:active~ul, .atcb-list:hover{visibility:visible;}.atcb-list {visibility: hidden;position: absolute;top: 100%;left: 0;width: 170px;z-index: 900;}.atcb-list, .atcb-item {list-style: none;margin: 0;padding: 0;background: #fff;}.atcb-item {float: none;text-align: left;}.atcb-item-link {text-decoration: none;outline: none;display: block;}.atcb-item.hover, .atcb-item:hover {position: relative;z-index: 900;cursor: pointer;text-decoration: none;outline: none;}.atc-style-menu-wb .atcb-list {width: 170px;border: 1px solid rgb(186,186,186);border-radius: 2px;box-shadow: 0 0 5px #AAA;}.atc-style-menu-wb .atcb-list, .atc-style-menu-wb .atcb-item {background: #fff;color: #000;}.atc-style-menu-wb .atcb-item, .atc-style-menu-wb .atcb-item-link {line-height: 1.3em;vertical-align: middle;zoom: 1;}.atc-style-menu-wb .atcb-item-link, .atc-style-menu-wb .atcb-item-link:hover, .atc-style-menu-wb .atcb-item-link:active, .atc-style-menu-wb .atcb-item-link:focus {color: #000;font-family: "Verdana";font-size: 14px;text-decoration: none;outline: none;padding: 5px 15px;}.atc-style-menu-wb .atcb-item-link:hover, .atc-style-menu-wb .atcb-item-link:active, .atc-style-menu-wb .atcb-item-link:focus {color: #fff;}.atc-style-menu-wb .atcb-item.hover, .atc-style-menu-wb .atcb-item:hover {background: rgb(66,129,244);}.press {line-height: 1.3;}.press .header {display: table;border-bottom: 1px solid #000;margin-bottom: 7px;width: 100%;}.press .more-link {display: table-cell;font-size:14px;text-align: right;}.press .more-link a {text-decoration: none;}.press .headline {color: #0079C2;font-weight: 400;line-height: 1.2;margin: 0;}.press .headline + .headline {margin-top: 10px;}.date {margin-bottom:0;}.press .date {font-size: 0.85em;font-weight: 300;margin: 0.3em 0 1.5em 0;}.event .date {margin-bottom:0;}.press .more, .press img {display: none;}.press img.rss {display: inline;}.item .date {font-size: 1.1em;font-weight: 500;margin-top: 2em;}.item .date + .headline {margin-top: .25em;}.event-list {list-style-type: none;margin-left: 0;padding-left: 0;}.event-list > li {margin-bottom:1.5em;}.event-list .date {font-weight: bold;margin-bottom: 0;text-transform: uppercase;}.event-list .event {font-weight: bold;line-height:1.3;margin-bottom: 0;}.event-list .location {line-height:1.3;margin-bottom: 0;}.event-list .addtocalendar > a {text-transform: uppercase;font-size: small;}div.event {margin-bottom:10px;}.marketing-stack {display: table;margin-bottom: 2em;min-height:555px;text-align: center;}.marketing-stack > div {display: table-row;}.marketing-stack > div > div {display: table-cell;}.marketing-stack > div:first-child > div {vertical-align: top;}.marketing-stack > div:last-child > div {vertical-align: bottom;}@media(min-width:768px) {.marketing-stack .img-fluid {width:100%;}}.media-block {margin-top: 1em;margin-bottom: 1em;font-size:1rem;}.media-block:after {clear:both;}.media-block h4 {margin:0.25em 0;}.media-block .header, .media-block h2.header, .media-block h3.header, .media-block h4.header, .media-block h5.header, .media-block h6.header {margin-top:0;}.media-block img + .header, .media-block img + h2.header, .media-block img + h3.header, .media-block img + h4.header {margin-top:1em;}.media-block .more a {text-decoration: none;}.media-block .more a:hover {text-decoration: underline;}.media-block img + ul, .media-block img + ol, .media-block img + p {margin-top: 0.5em;}.link-tile {text-decoration: none;}.link-tile {display: block;}.link-tile:hover {text-decoration: none;}.link-tile a {text-decoration: none;}.link-tile a:hover {text-decoration: none;}.link-tile:hover h2, .link-tile:hover h4 {color:#e07700;}.footnote {border-top: 1px solid #ccc;font-style: italic;margin-top: 1.62em;padding-top: 1em;}.table-sm-data, .table-data, .table.data {font-size: 80%;}.table-inline, .table.inline {display: inline;}.table-striped > tbody > tr:nth-child(2n+1) > td, .table-striped > tbody > tr:nth-child(2n+1) > th {background-color: #F5F5F5;}caption {font-size:22px;text-align:left;border-bottom: 3px solid #52BFFF;color: #004677;caption-side: top;padding:0;}table.dataTable.dtr-inline.collapsed > tbody > tr[role="row"] > td:first-child::before, table.dataTable.dtr-inline.collapsed > tbody > tr[role="row"] > th:first-child::before {background-color:#0079c2 !important;}#scrollUp {bottom: 0;right: 0;padding: 10px 20px;background-color: #0079c2;color: #fff;text-decoration: none;}.navbar {padding:0;}.dropdown-toggle::after {display:none;}@media (min-width:768px) {.searchbar-toggle {display: none;}}.program-header .navbar .navbar-collapse {border: none;}@media (min-width: 768px) {.program-header .navbar .navbar-collapse {border-top: 1px solid #d1d5d8;}}@media (min-width: 992px) {.program-header .navbar .navbar-collapse {border:none;}.program-header .navbar .navbar-collapse.xl-row {border-top: 1px solid #d1d5d8;}.program-header .navbar .navbar-collapse.rows {border-top: 1px solid #d1d5d8;}}@media (min-width: 1200px) {.program-header .navbar .navbar-collapse.xl-row {border-top:none }}.application .navbar-light .navbar-nav .dropdown-menu, .program-header .navbar-light .navbar-nav .dropdown-menu {left:auto;right:0;white-space: nowrap;}.application .navbar-light .navbar-toggler, .program-header .navbar-light .navbar-toggler {border: none;}.navbar-toggle-label {font-size: 0.8em;color: #fff;}#app-menu, #program-menu {background-color: #5e6a71;color:#fff;width: 100%;position: relative;}#app-menu, #program-menu:hover {background-color: #0079c2;}@media (min-width:768px) {#app-menu, #program-menu {width:auto }}#app-menu, #program-menu:before {content:"";background-color: #5e6a71;position: absolute;height: 100%;width: 4000px;left: -2000px;z-index: -1;}#app-menu, #program-menu:hover:before {background-color: #0079c2;}.custom-toggler .navbar-toggler-icon {background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgb(255,255,255)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 8h24M4 16h24M4 24h24'/%3E%3C/svg%3E");}.navbar-wrapper {min-height: 50px;}.navbar-wrapper a {text-decoration: none;}.navbar-wrapper.corporate-topnav .container {border-top:none;}@media (min-width:768px) {.navbar-wrapper .container {border-top: 1px solid #d1d5d8;}.navbar-toggler {display: none;}}.navbar-light .navbar-nav > li > a {border-top:3px solid transparent;color: #5e6a71;padding-top: 10px;padding-bottom: 10px;line-height:30px;}@media (min-width:768px) {.navbar-light .navbar-nav > li > a {text-transform: none;border-top:3px solid transparent;background-color: transparent;}}.application .navbar-light .navbar-nav > li > a, .program-header .navbar-light .navbar-nav > li > a {padding-top: 15px;padding-bottom: 15px;border-top:3px solid transparent;}.application .navbar-light .navbar-nav > li > a:hover, .application .navbar-light .navbar-nav > li > a:focus, .application .navbar-light .navbar-nav > li.active > a:hover, .application .navbar-light .navbar-nav > li.active > a:focus, .program-header .navbar-light .navbar-nav > li > a:hover, .program-header .navbar-light .navbar-nav > li > a:focus, .program-header .navbar-light .navbar-nav > li.active > a:hover, .program-header .navbar-light .navbar-nav > li.active > a:focus {background-color: #0B5E90;border-top: 3px solid transparent;color: #fff;}@media (min-width:768px) {.application .navbar-light .navbar-nav > li.active > a, .application .navbar-light .navbar-nav > li > a:hover, .application .navbar-light .navbar-nav > li > a:focus, .application .navbar-light .navbar-nav > li.active > a:hover, .application .navbar-light .navbar-nav > li.active > a:focus, .program-header .navbar-light .navbar-nav > li.active > a, .program-header .navbar-light .navbar-nav > li > a:hover, .program-header .navbar-light .navbar-nav > li > a:focus, .program-header .navbar-light .navbar-nav > li.active > a:hover, .program-header .navbar-light .navbar-nav > li.active > a:focus {background-color: #5E6A71;}}.navbar-light .navbar-nav > li > a .fa-angle-down:before {content: "\f107";}.navbar-light .navbar-nav > li.show > a .fa-angle-down:before {content: "\f106";}@media(min-width:768px) {.application .navbar-light .navbar-nav > li.show > a, .program-header .navbar-light .navbar-nav > li.show > a {border-top:3px solid transparent;}}.application .navbar-light .navbar-nav > li.show > a:hover, .application .navbar-light .navbar-nav > li.show > a:focus, .program-header .navbar-light .navbar-nav > li.show > a:hover, .program-header .navbar-light .navbar-nav > li.show > a:focus {background-color: #0B5E90;color: #fff;}@media(min-width:768px) {.application .navbar-light .navbar-nav > li.show > a:hover, .application .navbar-light .navbar-nav > li.show > a:focus, .program-header .navbar-light .navbar-nav > li.show > a:hover, .program-header .navbar-light .navbar-nav > li.show > a:focus {background-color: #5E6A71;}}.application .navbar-light .navbar-nav .show .dropdown-menu > li > a, .program-header .navbar-light .navbar-nav .show .dropdown-menu > li > a {background-color: #282e2e;border-bottom: 1px solid #000;color: #62d2ff;padding-top:10px;padding-bottom:10px;}@media(min-width:768px) {.application .navbar-light .navbar-nav .show .dropdown-menu > li > a, .program-header .navbar-light .navbar-nav .show .dropdown-menu > li > a {background-color: #5E6A71;border-bottom: 1px solid #4B545A;color: #fff;}}.application .navbar-light .navbar-nav .show .dropdown-menu > li > a:hover, .application .navbar-light .navbar-nav .show .dropdown-menu > li > a:focus, .program-header .navbar-light .navbar-nav .show .dropdown-menu > li > a:hover, .program-header .navbar-light .navbar-nav .show .dropdown-menu > li > a:focus {background-color: #0B5E90;color: #fff;}.application .navbar-light .navbar-nav .show .dropdown-menu > li > a .fa-home, .program-header .navbar-light .navbar-nav .show .dropdown-menu > li > a .fa-home {position: absolute;right:10px;top:15px;}.application .navbar-light .navbar-nav .show .dropdown-menu > li > a, .program-header .navbar-light .navbar-nav .show .dropdown-menu > li > a {position: relative;}.application .navbar-light .navbar-nav .dropdown > ul ul, .program-header .navbar-light .navbar-nav .dropdown > ul ul {padding-left:0;list-style-type: none;}.application .navbar-light .navbar-nav .dropdown > ul ul li, .program-header .navbar-light .navbar-nav .dropdown > ul ul li {background-color: #282e2e;}.application .navbar-light .navbar-nav .dropdown > ul ul a, .application .navbar-light .navbar-nav .dropdown > ul ul a:visited, .program-header .navbar-light .navbar-nav .dropdown > ul ul a, .program-header .navbar-light .navbar-nav .dropdown > ul ul a:visited {color: #62d2ff;display: block;text-decoration: none;padding-left:40px;padding-top:10px;padding-bottom:10px;}.application .navbar-light .navbar-nav .dropdown > ul ul a:hover, .program-header .navbar-light .navbar-nav .dropdown > ul ul a:hover {background-color: #0B5E90;color: #fff;}.navbar, .navbar-nav, .navbar-collapse {border:0;margin-bottom:0;min-height: 0;}ul.navbar-nav {margin-top:0;margin-bottom: 0;}ul.dropdown-menu {border:0;padding-top: 0;padding-bottom:0;margin-top:0;}.corporate-topnav .container {padding-left: 0;padding-right: 0;}@media (min-width:768px) {.corporate-topnav .container {padding-left: 15px;padding-right: 15px;}}.corporate-topnav .navbar {padding: 0.5rem 1rem 0 1rem;}.navbar-wrapper.corporate-topnav {border-bottom: 1px solid #d1d5d8;}.corporate-topnav .navbar-inverse {text-align: center;background-color: transparent;}.corporate-topnav .navbar-expand .navbar-nav > li > a {display: block;font-size: 0.75em;padding-left: 0.5em;padding-right: 0.5em;text-transform: none;}@media(min-width: 576px) {.corporate-topnav .navbar-expand .navbar-nav > li > a {font-size: 0.8em;padding-left: 0.8em;padding-right: 0.8em;}}.corporate-topnav .navbar-expand .navbar-nav > li > a {background-color: transparent;}.corporate-topnav .navbar-expand .navbar-nav > li > a:hover, .corporate-topnav .navbar-expand .navbar-nav > li > a:focus {background-color:#0079c2;color: #fff;}.navbar-light .navbar-nav.ml-auto .nav-link {color: #0079C2;}.navbar-light .navbar-nav .nav-link {color: #5e6a71;background-color: #ededed;padding: 15px;}.navbar-light .navbar-nav .active > .nav-link, .navbar-light .navbar-nav .nav-link.active, .navbar-light .navbar-nav .nav-link.show, .navbar-light .navbar-nav .show > .nav-link {background-color:#0079c2;color: #fff;}@media(min-width:992px) {.navbar-wrapper.corporate-topnav {margin-top:-17px;}}.corporate-topnav .navbar-expand .navbar-nav > li.active > a, .corporate-topnav .navbar-expand .navbar-nav > li.active > a:hover {background-color:#0079c2;}.corporate-topnav .navbar-expand .navbar-nav > li.open > a {background-color: #0079c2;}.corporate-topnav .navbar-expand .navbar-nav .show .dropdown-menu > li > a {background-color: #5E6A71;border-bottom: 1px solid #4B545A;display: block !important;color: #fff;}@media(min-width:768px) {.corporate-topnav .navbar-expand .navbar-nav .show .dropdown-menu > li > a {background-color: #5E6A71;border-bottom: 1px solid #4B545A;color: #fff;}}.corporate-topnav .navbar-expand .navbar-nav .show .dropdown-menu > li > a:hover, .corporate-topnav .navbar-expand .navbar-nav .show .dropdown-menu > li > a:focus {background-color: #0B5E90;color: #fff;}@media (min-width:768px) {.corporate-topnav .navbar-expand .navbar-nav > li > a {font-size: 1em;}}.corporate-topnav .navbar-expand .navbar-nav li.dropdown {position: static;}.corporate-topnav .navbar-expand .navbar-nav .show .dropdown-menu {background-color: #5E6A71;border-top:0;font-size: 1em;width: 100%;}@media (min-width:768px) {.corporate-topnav .navbar-expand .navbar-nav .show .dropdown-menu {display:block;left:auto;width:auto;}}.banner-logo {width: 100%;border-bottom: none;min-height:60px;}header .logo {background: url('client/img/nrel-logo-web.svg') no-repeat center;height: 75px;background-size: 190px;margin: 0 0 10px 0;}.program-header {background-color: transparent;}.program-header .navbar-inverse {border-top: 1px solid #d1d5d8;}.program-header .container {padding-right: 0;padding-left: 0;}header.notopnav {border-bottom: 1px solid #d1d5d8;margin:0 0 1.5em 0;}@media only screen and (min-width : 480px) {header .logo {height: 85px;background-size: 214px;margin: 0 0 10px 0;}}@media (min-width: 768px) {header .logo {background: url('client/img/nrel-logo-web.svg') no-repeat left;height:130px;background-size: 328px;}.program-header .container {padding-right: 15px;padding-left: 15px;}.banner-logo {min-height:92px;}.program-header {background-color: #ededed;}}@media only screen and (min-width : 1200px) {.program-header .navbar-inverse {border-top: none;}}.searchbar-toggle {border: 1px solid #5e6a71 ;border-radius:5px;color: #5e6a71 ;margin:15px;padding: 8px 6px 8px 6px;position: absolute;right: 0;top:0px;}@media (min-width: 768px) {#searchbar-collapse {background-color: transparent;margin-left: 0px;margin-right: 0px;}#searchbar-collapse:before {background-color:transparent;}}@media (min-width: 768px) {#searchbar-collapse:after {display: none;}}.searchbar-form {padding-top: 7px;padding-bottom: 7px;}@media (min-width: 768px) {.searchbar-form {background-color: transparent;float:right;}}.searchbar-form button {background-color: #D1D5D8;border-color: #D1D5D8;color: #000;height:40px;text-transform: uppercase;}@media (min-width: 768px) {header .navbar-collapse, header .navbar-form {padding-left:0;padding-right: 0;}}header nav.navbar {border: none;box-shadow: none;text-align: center;}.searchbar-form .form-group {display: inline-block;margin-bottom: 0;vertical-align: middle;}.searchbar-form .form-control {display: inline-block;width: 200px;vertical-align: middle;box-shadow: none;height:40px;}@media (max-width: 350px) {.searchbar-form .form-control {width: 160px;}}#page-tools {margin: 2em 0;}footer {background-color: #F5F5F5;color: #000;font-size: 14px;font-weight: 400;line-height: 1.2;padding-bottom: 5em;}footer .footertop {background-color: #D1D5D8;padding: 2em 0;}footer .global div {margin: 1em 0 0 0;line-height:1.3;}@media (min-width: 992px) {footer .global div {margin: 0 2em;}footer .global div:first-child {margin: 0 2em 0 0;}footer .global div:last-child {margin: 0 0 0 2em;}}footer .footerbottom {padding-top:2em;}footer .globalsecondary div, footer .nrel-attr {font-size: 0.75rem;}footer .nrel-attr a:link, footer .nrel-attr a:visited {text-decoration:underline;}footer .nrel-attr a:hover, footer .nrel-attr a:active {color: #0071b8;}footer .header {border-bottom:1px solid #5a5b5b;font-size: 14px;margin-top: 1em;text-transform: uppercase;}footer .only-nrel {font-size: 10px;}footer .social-links {font-size: 14px;margin:0 0 1em 0;}footer .social-links li {padding: 0;}footer a, footer a:visited {color: #000;text-decoration: none;}footer a:hover, footer a:active {color:#000;text-decoration: underline;}footer .logo {display: block;max-width:100px;margin-top: 1em;}footer ul {list-style-type: none;margin-left:0;margin-bottom: 0;padding-left: 0;}footer ul li {line-height: 1.25em;margin-bottom: 0.55em;}footer ul.dotlist li:before {content: "\00a0 \00b7 \00a0";}footer ul.dotlist li:first-child:before {content: "";}@media(min-width:768px) {footer .ft-border-right {border-right: 1px solid #5a5b5b;}footer .social-links {font-size: 14px;margin:0;}footer .header {margin-top: 0;}}#perPage {margin-left: 10px;}.results-header .form-inline input[type=text] {width: 99%;}@media(min-width:768px){.results-header .form-inline [class^="col-"] {padding-right:0;}.results-header .form-inline [class^="col-"]+[class^="col-"] {padding-left: 0;}}.results-header a {text-decoration: none;}.results-header a:hover {text-decoration: underline;}.results-header p, .results-header label {font-size: 18px;}.results-header .results-notes {margin: 1.5em 0;}.results-header .results-notes p {margin:0;}.results-header .results-total p {margin-bottom: 0;}.results-header .highlighted {color: #c60;font-style: italic;font-weight: bold;}.results-list {margin-top:1.5em;padding-top:1.5em;border-top: 1px solid #ccc;}.results-highlighted {font-weight: bold;}.results-item {margin-bottom: 1.5em;padding-bottom: 1.5em;border-bottom: 1px solid #ccc;text-decoration: none;}.results-item .summary {color: initial;}.results-item .footer {color:#5E6A71;}.results-pager .pagination {margin: 0;}.results-pager p {padding-top:.5em;}.results-pager a {text-decoration: none;}.results-pager a:hover {text-decoration: underline;}.results-pager .glyphicon {font-size: 75%;}figure {display: table;margin:1em auto;}figure img {display: block;}figcaption {display: table-caption;caption-side: bottom;font-size: 12px;color: #757575;margin-top: .5em;}.hpf6df8a6f-3fb6-4eba-b28d-c99912998305{display:none;margin-left:-1000px;}.hpe2cd04f6-da55-4773-80a2-c25c4ca203c4{display:none;margin-left:-1000px;}.content-list-widget {font-weight: 400;font-size: 1.1em;line-height: 1.2;margin-bottom: 3em;}.content-list-widget .header-box {background-color: #ededed;border-bottom: 3px solid #00b5ef;}.content-list-widget .header-box .title {color: #000;font-size: 1.35em;margin-bottom: 0;padding: .8em;text-transform: none;}.content-list-widget .header-box .title span {font-weight: 500;text-transform:uppercase;color:#0079c5;}.content-list-widget .content-box {border: 1px solid #D1D5D8;padding: 1em;margin: 0;}.content-list-widget .list {padding-left: 1.35em;}.content-list-widget .title+.list {border-top: 1px solid #D1D5D8;margin-top: .5em;padding-top: .75em;}.content-list-widget .list, .content-list-widget .btn-link {text-decoration: none;text-transform: none;}.content-list-widget .btn-link:visited {color:#0079c2;}.content-list-widget .text-left .btn-link {margin-left: 0;padding-left: 0;}.content-list-widget .list .headline, .content-list-widget .list .date {margin-bottom: 0;}.content-list-widget .footnotes{border-top: 1px solid #D1D5D8;padding-top: 1em;}.content-list-widget .text-right {text-align: inherit;}@media (min-width:768px) {.content-list-widget .text-right {text-align: right;}.addthis_inline_share_toolbox_hq4v {margin-left:0px;}}.bg-blue {background-color:#0079C2;border-bottom:none;}.blogpost h2 {margin:0.5em 0;}.addthis_inline_share_toolbox_hq4v {margin-left:-10px;}blockquote {color: #757575;border-top: 5px solid #00A4E4;border-bottom: 5px solid #00A4E4;padding: 1em 0;}blockquote.green {color: #757575;border-top: 5px solid #8cc63f;border-bottom: 5px solid #8cc63f;padding: 1em 0;} \ No newline at end of file diff --git a/frontend/client/img/alliance-logo_black.png b/frontend/client/img/alliance-logo_black.png deleted file mode 100644 index 526d457..0000000 Binary files a/frontend/client/img/alliance-logo_black.png and /dev/null differ diff --git a/frontend/client/img/bg-pattern.png b/frontend/client/img/bg-pattern.png deleted file mode 100755 index cfd7911..0000000 Binary files a/frontend/client/img/bg-pattern.png and /dev/null differ diff --git a/frontend/client/img/doe-eere.png b/frontend/client/img/doe-eere.png deleted file mode 100644 index 6d18ebe..0000000 Binary files a/frontend/client/img/doe-eere.png and /dev/null differ diff --git a/frontend/client/img/icon_minus.svg b/frontend/client/img/icon_minus.svg deleted file mode 100755 index bc82cd8..0000000 --- a/frontend/client/img/icon_minus.svg +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - -]> - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/client/img/icon_plus_blue.svg b/frontend/client/img/icon_plus_blue.svg deleted file mode 100755 index f417dd7..0000000 --- a/frontend/client/img/icon_plus_blue.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/frontend/client/img/logo-doe-footer.png b/frontend/client/img/logo-doe-footer.png deleted file mode 100755 index 8ab42ab..0000000 Binary files a/frontend/client/img/logo-doe-footer.png and /dev/null differ diff --git a/frontend/client/img/nrel-logo-web.svg b/frontend/client/img/nrel-logo-web.svg deleted file mode 100644 index 56d1914..0000000 --- a/frontend/client/img/nrel-logo-web.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/client/img/touch-icon-ipad-retina.png b/frontend/client/img/touch-icon-ipad-retina.png deleted file mode 100755 index d5ed0f8..0000000 Binary files a/frontend/client/img/touch-icon-ipad-retina.png and /dev/null differ diff --git a/frontend/client/img/touch-icon-ipad.png b/frontend/client/img/touch-icon-ipad.png deleted file mode 100755 index 9be41a5..0000000 Binary files a/frontend/client/img/touch-icon-ipad.png and /dev/null differ diff --git a/frontend/client/img/touch-icon-iphone-retina.png b/frontend/client/img/touch-icon-iphone-retina.png deleted file mode 100755 index 83d1b79..0000000 Binary files a/frontend/client/img/touch-icon-iphone-retina.png and /dev/null differ diff --git a/frontend/client/img/touch-icon-iphone.png b/frontend/client/img/touch-icon-iphone.png deleted file mode 100755 index 90cae56..0000000 Binary files a/frontend/client/img/touch-icon-iphone.png and /dev/null differ diff --git a/frontend/client/js/app.js b/frontend/client/js/app.js deleted file mode 100755 index 83cc08c..0000000 --- a/frontend/client/js/app.js +++ /dev/null @@ -1,48 +0,0 @@ -'use strict'; - -/* - * moakley - * Set site-level variables (required) - */ - $(document).ready( function(){ - - window.nrel = $.extend({}, window.nrel); // Merge in page level variables if they are set - window.nrel.pagevars = $.extend({}, window.nrel.pagevars); // (in case window.nrel isn't defined) - - window.nrel.pagevars.sitename = 'AppName'; - - var $navlink, - $navitem, - slash, - nrel, - pv; - - // shorthand alias for our page variables - nrel = window.nrel || {}; - pv = nrel.pagevars || {}; - - - pv.pagename = $('h1').text(); - slash = location.pathname.lastIndexOf('/') + 1; - - pv.pageurl = location.pathname; // /foo/bar/baz/boink.html - pv.siteurl = location.pathname.substr(0,slash); // /foo/bar/baz/ - pv.filename = location.pathname.substr(slash) ; // boink.html - - // catch situations where the url ends in a slash, with index.html implied - if( ! pv.filename.length) { - pv.filename = 'index.html'; // this could be index.php or index.cfm, or ... - } - - - /* - * Contact Us footer link - * if the site doesn't defer to the globalwebmaster, use the local one - */ - if( !pv.globalwebmaster && pv.sitename ) { - $('#contact-link').attr( 'href', pv.siteurl + 'contacts.html' ); - } else { - $('#contact-link').attr( 'href', '/webmaster.html' ); - } - -}); diff --git a/frontend/client/js/app.min.js b/frontend/client/js/app.min.js deleted file mode 100755 index 206289e..0000000 --- a/frontend/client/js/app.min.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! application template scripts Mon June 22 2015 07:48:15 */ -"use strict";$(document).ready(function(){window.nrel=$.extend({},window.nrel),window.nrel.pagevars=$.extend({},window.nrel.pagevars),window.nrel.pagevars.sitename="AppName";var e,a,n;a=window.nrel||{},n=a.pagevars||{},n.pagename=$("h1").text(),e=location.pathname.lastIndexOf("/")+1,n.pageurl=location.pathname,n.siteurl=location.pathname.substr(0,e),n.filename=location.pathname.substr(e),n.filename.length||(n.filename="index.html"),!n.globalwebmaster&&n.sitename?$("#contact-link").attr("href",n.siteurl+"contacts.html"):$("#contact-link").attr("href","/webmaster.html")}); \ No newline at end of file diff --git a/frontend/client/js/iconomatic/.gitignore b/frontend/client/js/iconomatic/.gitignore deleted file mode 100755 index f97c964..0000000 --- a/frontend/client/js/iconomatic/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -# -# NPM -# -node_modules diff --git a/frontend/client/js/iconomatic/.jshintrc b/frontend/client/js/iconomatic/.jshintrc deleted file mode 100755 index e51ca6c..0000000 --- a/frontend/client/js/iconomatic/.jshintrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "strict" : true, // Require `use strict` pragma in every file. - "white" : false, // Check against strict whitespace and indentation rules. - "jquery" : true, - "browser" : true, // Standard browser globals e.g. `window`, `document`. - "laxcomma" : true // Suppress warnings about comma-first coding style. -} diff --git a/frontend/client/js/iconomatic/jquery.iconomatic.js b/frontend/client/js/iconomatic/jquery.iconomatic.js deleted file mode 100755 index 6079239..0000000 --- a/frontend/client/js/iconomatic/jquery.iconomatic.js +++ /dev/null @@ -1,198 +0,0 @@ -;(function ( $, window, document, undefined ) { - - 'use strict'; - - // Default file types to label with icons - var fileTypes = { - 'avi' : { 'class' : 'fa-file-video-o', 'type' : 'Video'} - , 'doc' : { 'class' : 'fa-file-word-o', 'type' : 'Microsoft Word'} - , 'docx': { 'class' : 'fa-file-word-o', 'type' : 'Microsoft Word'} - , 'gif' : { 'class' : 'fa-file-image-o', 'type' : 'GIF'} - , 'jpg' : { 'class' : 'fa-file-image-o', 'type' : 'JPG'} - , 'm3u' : { 'class' : 'fa-file-audio-o', 'type' : 'Audio'} - , 'mov' : { 'class' : 'fa-file-video-o', 'type' : 'Video'} - , 'mp3' : { 'class' : 'fa-file-audio-o', 'type' : 'Audio'} - , 'mp4' : { 'class' : 'fa-file-video-o', 'type' : 'Video'} - , 'mpg' : { 'class' : 'fa-file-video-o', 'type' : 'Video'} - , 'mpeg': { 'class' : 'fa-file-video-o', 'type' : 'Video'} - , 'pdf' : { 'class' : 'fa-file-pdf-o', 'type' : 'PDF'} - , 'ppt' : { 'class' : 'fa-file-powerpoint-o', 'type' : 'Microsoft PowerPoint'} - , 'pptx': { 'class' : 'fa-file-powerpoint-o', 'type' : 'Microsoft PowerPoint'} - , 'wmv' : { 'class' : 'fa-file-video-o', 'type' : 'Video'} - , 'txt' : { 'class' : 'fa-file-text-o', 'type' : 'Text'} - , 'xls' : { 'class' : 'fa-file-excel-o', 'type' : 'Microsoft Excel'} - , 'xlsb': { 'class' : 'fa-file-excel-o', 'type' : 'Microsoft Excel'} - , 'xlsx': { 'class' : 'fa-file-excel-o', 'type' : 'Microsoft Excel'} - , 'xlsm': { 'class' : 'fa-file-excel-o', 'type' : 'Microsoft Excel'} - , 'zip' : { 'class' : 'fa-file-archive-o', 'type' : 'ZIP Archive'} - }; - - - var pluginName = 'iconomatic'; - - // Plugin defaults - var defaults = { - ajax: false - ,dataMode: false - ,dataAttr: 'iconomatic' - ,iconClass: 'fileIcon' - ,filesObj: fileTypes - }; - - // Constructor - function Iconomatic( element, options ) { - this.element = element; - - this.options = $.extend( {}, defaults, options ); - - this._defaults = defaults; - this._name = pluginName; - - this.init(); - } - - Iconomatic.prototype = { - - init: function() { - var links, - context; - - context = $('body'); // todo: move this into settings - - links = this.getLinks( context ); - this.addIcons( links ); - - if( this.options.ajax ) { - this.enableAjax(); - } - }, - - /* - * Inspect all tags within our region - * Return the ones with relevant file extensions or data attributes - * - */ - getLinks: function( region ) { - var opts, - types, - dataAttr, - links; - - opts = this.options; - types = opts.filesObj; - dataAttr = 'data-' + opts.dataAttr; - - links = []; - - $(region).find('a').filter(function(){ - return !$(this).attr('data-iconomatic-tagged'); // remove any previously tagged - }).each( function( idx, link ){ - var href, - ext; - - - href = $(link).attr('href'); - - if( typeof href !== 'undefined' && href !== null && href !== '' ) { - - ext = href.toLowerCase().split('.').splice( -1, 1 ).toString(); // this could be more elegant - - if( ext in types ) { - $(link).attr('data-iconomatic-tagged', ext); // tag our valid links - links.push( link ); - } - } - if( opts.dataMode ) { - - // copy the users data attribute to our data attribute - if( $(link).attr(dataAttr) ) { - - $(link).attr('data-iconomatic-tagged', function(){ // tag our valid links - return $(this).attr( dataAttr ); - }); - - links.push( link ); - } - } - }); - - return links; - }, - - /* - * Use the mutation observer to watch for changes in our doc. - * Find the Links and add the icons in the changed region. - * - */ - enableAjax: function(){ - var links, - MutationObserver, - region, - observer, - self = this; - - if( window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver ) { - MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; - } else { - return true; // BAIL ON ', attrs ).appendTo(link); // finally do the work! - } - }); - } - - }; - - $.fn[pluginName] = function ( options ) { - return this.each(function () { - if (!$.data(this, 'plugin_' + pluginName)) { - $.data(this, 'plugin_' + pluginName, new Iconomatic( this, options )); - } - }); - }; - - -})( jQuery, window, document ); diff --git a/frontend/client/js/iconomatic/jquery.iconomatic.min.js b/frontend/client/js/iconomatic/jquery.iconomatic.min.js deleted file mode 100755 index 2644eb2..0000000 --- a/frontend/client/js/iconomatic/jquery.iconomatic.min.js +++ /dev/null @@ -1,10 +0,0 @@ -/*! -* Project: Iconomatic -* Version: 2.0.0 -* Description: Detect links to native files and append the proper icon image after the link -* Author: Michael Oakley -* Build Date: 2016-05-12 -*/ - -(function(a,b,c,d){"use strict";var e={avi:{"class":"fa-file-video-o",type:"Video"},doc:{"class":"fa-file-word-o",type:"Microsoft Word"},docx:{"class":"fa-file-word-o",type:"Microsoft Word"},gif:{"class":"fa-file-image-o",type:"GIF"},jpg:{"class":"fa-file-image-o",type:"JPG"},m3u:{"class":"fa-file-audio-o",type:"Audio"},mov:{"class":"fa-file-video-o",type:"Video"},mp3:{"class":"fa-file-audio-o",type:"Audio"},mp4:{"class":"fa-file-video-o",type:"Video"},mpg:{"class":"fa-file-video-o",type:"Video"},mpeg:{"class":"fa-file-video-o",type:"Video"},pdf:{"class":"fa-file-pdf-o",type:"PDF"},ppt:{"class":"fa-file-powerpoint-o",type:"Microsoft PowerPoint"},pptx:{"class":"fa-file-powerpoint-o",type:"Microsoft PowerPoint"},wmv:{"class":"fa-file-video-o",type:"Video"},txt:{"class":"fa-file-text-o",type:"Text"},xls:{"class":"fa-file-excel-o",type:"Microsoft Excel"},xlsb:{"class":"fa-file-excel-o",type:"Microsoft Excel"},xlsx:{"class":"fa-file-excel-o",type:"Microsoft Excel"},xlsm:{"class":"fa-file-excel-o",type:"Microsoft Excel"},zip:{"class":"fa-file-archive-o",type:"ZIP Archive"}};var f="iconomatic";var g={ajax:false,dataMode:false,dataAttr:"iconomatic",iconClass:"fileIcon",filesObj:e};function h(b,c){this.element=b;this.options=a.extend({},g,c);this._defaults=g;this._name=f;this.init()}h.prototype={init:function(){var b,c;c=a("body");b=this.getLinks(c);this.addIcons(b);if(this.options.ajax){this.enableAjax()}},getLinks:function(b){var c,d,e,f;c=this.options;d=c.filesObj;e="data-"+c.dataAttr;f=[];a(b).find("a").filter(function(){return!a(this).attr("data-iconomatic-tagged")}).each(function(b,g){var h,i;h=a(g).attr("href");if(typeof h!=="undefined"&&h!==null&&h!==""){i=h.toLowerCase().split(".").splice(-1,1).toString();if(i in d){a(g).attr("data-iconomatic-tagged",i);f.push(g)}}if(c.dataMode){if(a(g).attr(e)){a(g).attr("data-iconomatic-tagged",function(){return a(this).attr(e)});f.push(g)}}});return f},enableAjax:function(){var a,d,e,f,g=this;if(b.MutationObserver||b.WebKitMutationObserver||b.MozMutationObserver){d=b.MutationObserver||b.WebKitMutationObserver||b.MozMutationObserver}else{return true}e=c.querySelector("body");f=new d(function(b){b.forEach(function(b){if(b.type==="childList"){a=g.getLinks(b.target);g.addIcons(a)}})});f.observe(e,{childList:true,subtree:true})},addIcons:function(b){var c=this.options;a(b).filter(function(){return a(this).has("img").length===0}).each(function(b,d){var e,f,g;e=a(d).data("iconomatic-tagged");f=typeof c.filesObj[e]!=="undefined"?c.filesObj[e].class:false;if(f){g={"class":c.iconClass+" fa "+f,title:c.filesObj[e].type};a("",g).appendTo(d)}})}};a.fn[f]=function(b){return this.each(function(){if(!a.data(this,"plugin_"+f)){a.data(this,"plugin_"+f,new h(this,b))}})}})(jQuery,window,document); -//# sourceMappingURL=jquery.iconomatic.min.map \ No newline at end of file diff --git a/frontend/client/js/jquery.gridster.min.js b/frontend/client/js/jquery.gridster.min.js deleted file mode 100644 index 0c7b018..0000000 --- a/frontend/client/js/jquery.gridster.min.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! gridster.js - v0.7.0 - 2016-02-26 - * https://dsmorse.github.io/gridster.js/ - Copyright (c) 2016 ducksboard; Licensed MIT */ !function(a,b){"use strict";"object"==typeof exports?module.exports=b(require("jquery")):"function"==typeof define&&define.amd?define("gridster-coords",["jquery"],b):a.GridsterCoords=b(a.$||a.jQuery)}(this,function(a){"use strict";function b(b){return b[0]&&a.isPlainObject(b[0])?this.data=b[0]:this.el=b,this.isCoords=!0,this.coords={},this.init(),this}var c=b.prototype;return c.init=function(){this.set(),this.original_coords=this.get()},c.set=function(a,b){var c=this.el;if(c&&!a&&(this.data=c.offset(),this.data.width=c[0].scrollWidth,this.data.height=c[0].scrollHeight),c&&a&&!b){var d=c.offset();this.data.top=d.top,this.data.left=d.left}var e=this.data;return void 0===e.left&&(e.left=e.x1),void 0===e.top&&(e.top=e.y1),this.coords.x1=e.left,this.coords.y1=e.top,this.coords.x2=e.left+e.width,this.coords.y2=e.top+e.height,this.coords.cx=e.left+e.width/2,this.coords.cy=e.top+e.height/2,this.coords.width=e.width,this.coords.height=e.height,this.coords.el=c||!1,this},c.update=function(b){if(!b&&!this.el)return this;if(b){var c=a.extend({},this.data,b);return this.data=c,this.set(!0,!0)}return this.set(!0),this},c.get=function(){return this.coords},c.destroy=function(){this.el.removeData("coords"),delete this.el},a.fn.coords=function(){if(this.data("coords"))return this.data("coords");var a=new b(this);return this.data("coords",a),a},b}),function(a,b){"use strict";"object"==typeof exports?module.exports=b(require("jquery")):"function"==typeof define&&define.amd?define("gridster-collision",["jquery","gridster-coords"],b):a.GridsterCollision=b(a.$||a.jQuery,a.GridsterCoords)}(this,function(a,b){"use strict";function c(b,c,e){this.options=a.extend(d,e),this.$element=b,this.last_colliders=[],this.last_colliders_coords=[],this.set_colliders(c),this.init()}var d={colliders_context:document.body,overlapping_region:"C"};c.defaults=d;var e=c.prototype;return e.init=function(){this.find_collisions()},e.overlaps=function(a,b){var c=!1,d=!1;return(b.x1>=a.x1&&b.x1<=a.x2||b.x2>=a.x1&&b.x2<=a.x2||a.x1>=b.x1&&a.x2<=b.x2)&&(c=!0),(b.y1>=a.y1&&b.y1<=a.y2||b.y2>=a.y1&&b.y2<=a.y2||a.y1>=b.y1&&a.y2<=b.y2)&&(d=!0),c&&d},e.detect_overlapping_region=function(a,b){var c="",d="";return a.y1>b.cy&&a.y1b.y1&&a.y2b.cx&&a.x1b.x1&&a.x2f;f++)-1===a.inArray(e[f],b)&&c.call(this,e[f]);for(var h=0,i=b.length;i>h;h++)-1===a.inArray(b[h],e)&&d.call(this,b[h])},e.find_collisions=function(b){for(var c=this,d=this.options.overlapping_region,e=[],f=[],g=this.colliders||this.$colliders,h=g.length,i=c.$element.coords().update(b||!1).get();h--;){var j=c.$colliders?a(g[h]):g[h],k=j.isCoords?j:j.coords(),l=k.get(),m=c.overlaps(i,l);if(m){var n=c.detect_overlapping_region(i,l);if(n===d||"all"===d){var o=c.calculate_overlapped_area_coords(i,l),p=c.calculate_overlapped_area(o);if(0!==p){var q={area:p,area_coords:o,region:n,coords:l,player_coords:i,el:j};c.options.on_overlap&&c.options.on_overlap.call(this,q),e.push(k),f.push(q)}}}}return(c.options.on_overlap_stop||c.options.on_overlap_start)&&this.manage_colliders_start_stop(e,c.options.on_overlap_start,c.options.on_overlap_stop),this.last_colliders_coords=e,f},e.get_closest_colliders=function(a){var b=this.find_collisions(a);return b.sort(function(a,b){return"C"===a.region&&"C"===b.region?a.coords.y1this.player_max_left?e=this.player_max_left:e=q&&(l=n+h,t>l&&(this.$scroll_container[i](l),this["scroll_offset_"+a]+=h)),r>=s&&(l=n-h,l>0&&(this.$scroll_container[i](l),this["scroll_offset_"+a]-=h)),this},j.manage_scroll=function(a){this.scroll_in("x",a),this.scroll_in("y",a)},j.calculate_dimensions=function(){this.scroller_height=this.$scroll_container.height(),this.scroller_width=this.$scroll_container.width()},j.drag_handler=function(b){if(!this.disabled&&(1===b.which||f)&&!this.ignore_drag(b)){var c=this,d=!0;return this.$player=a(b.currentTarget),this.el_init_pos=this.get_actual_pos(this.$player),this.mouse_init_pos=this.get_mouse_pos(b),this.offsetY=this.mouse_init_pos.top-this.el_init_pos.top,this.$document.on(this.pointer_events.move,function(a){var b=c.get_mouse_pos(a),e=Math.abs(b.left-c.mouse_init_pos.left),f=Math.abs(b.top-c.mouse_init_pos.top);return e>c.options.distance||f>c.options.distance?d?(d=!1,c.on_dragstart.call(c,a),!1):(c.is_dragging===!0&&c.on_dragmove.call(c,a),!1):!1}),f?void 0:!1}},j.on_dragstart=function(a){if(a.preventDefault(),this.is_dragging)return this;this.drag_start=this.is_dragging=!0;var b=this.$container.offset();return this.baseX=Math.round(b.left),this.baseY=Math.round(b.top),"clone"===this.options.helper?(this.$helper=this.$player.clone().appendTo(this.$container).addClass("helper"),this.helper=!0):this.helper=!1,this.scroll_container_offset_y=this.$scroll_container.scrollTop(),this.scroll_container_offset_x=this.$scroll_container.scrollLeft(),this.el_init_offset=this.$player.offset(),this.player_width=this.$player.width(),this.set_limits(this.options.container_width),this.options.start&&this.options.start.call(this.$player,a,this.get_drag_data(a)),!1},j.on_dragmove=function(a){var b=this.get_drag_data(a);this.options.autoscroll&&this.manage_scroll(b),this.options.move_element&&(this.helper?this.$helper:this.$player).css({position:"absolute",left:b.position.left,top:b.position.top});var c=this.last_position||b.position;return b.prev_position=c,this.options.drag&&this.options.drag.call(this.$player,a,b),this.last_position=b.position,!1},j.on_dragstop=function(a){var b=this.get_drag_data(a);return this.drag_start=!1,this.options.stop&&this.options.stop.call(this.$player,a,b),this.helper&&this.options.remove_helper&&this.$helper.remove(),!1},j.on_select_start=function(a){return this.disabled||this.ignore_drag(a)?void 0:!1},j.enable=function(){this.disabled=!1},j.disable=function(){this.disabled=!0},j.destroy=function(){this.disable(),this.$container.off(this.ns),this.$document.off(this.ns),d.off(this.ns),a.removeData(this.$container,"drag")},j.ignore_drag=function(b){return this.options.handle?!a(b.target).is(this.options.handle):a.isFunction(this.options.ignore_dragging)?this.options.ignore_dragging(b):this.options.resize?!a(b.target).is(this.options.items):a(b.target).is(this.options.ignore_dragging.join(", "))},a.fn.gridDraggable=function(a){return new b(this,a)},a.fn.dragg=function(c){return this.each(function(){a.data(this,"drag")||a.data(this,"drag",new b(this,c))})},b}),function(a,b){"use strict";"object"==typeof exports?module.exports=b(require("jquery"),require("./jquery.draggable.js"),require("./jquery.collision.js"),require("./jquery.coords.js"),require("./utils.js")):"function"==typeof define&&define.amd?define(["jquery","gridster-draggable","gridster-collision"],b):a.Gridster=b(a.$||a.jQuery,a.GridsterDraggable,a.GridsterCollision)}(this,function(a,b,c){"use strict";function d(b,c){this.options=a.extend(!0,{},g,c),this.options.draggable=this.options.draggable||{},this.options.draggable=a.extend(!0,{},this.options.draggable,{scroll_container:this.options.scroll_container}),this.$el=a(b),this.$scroll_container=this.options.scroll_container===window?a(window):this.$el.closest(this.options.scroll_container),this.$wrapper=this.$el.parent(),this.$widgets=this.$el.children(this.options.widget_selector).addClass("gs-w"),this.$changed=a([]),this.w_queue={},this.is_responsive()?this.min_widget_width=this.get_responsive_col_width():this.min_widget_width=this.options.widget_base_dimensions[0],this.min_widget_height=this.options.widget_base_dimensions[1],this.is_resizing=!1,this.min_col_count=this.options.min_cols,this.prev_col_count=this.min_col_count,this.generated_stylesheets=[],this.$style_tags=a([]),this.options.auto_init&&this.init()}function e(a){for(var b=["col","row","size_x","size_y"],c={},d=0,e=b.length;e>d;d++){var f=b[d];if(!(f in a))throw new Error("Not exists property `"+f+"`");var g=a[f];if(!g||isNaN(g))throw new Error("Invalid value of `"+f+"` property");c[f]=+g}return c}var f=a(window),g={namespace:"",widget_selector:"li",static_class:"static",widget_margins:[10,10],widget_base_dimensions:[400,225],extra_rows:0,extra_cols:0,min_cols:1,max_cols:1/0,min_rows:1,max_rows:15,autogenerate_stylesheet:!0,avoid_overlapped_widgets:!0,auto_init:!0,center_widgets:!1,responsive_breakpoint:!1,scroll_container:window,shift_larger_widgets_down:!0,move_widgets_down_only:!1,shift_widgets_up:!0,show_element:function(a,b){b?a.fadeIn(b):a.fadeIn()},hide_element:function(a,b){b?a.fadeOut(b):a.fadeOut()},serialize_params:function(a,b){return{col:b.col,row:b.row,size_x:b.size_x,size_y:b.size_y}},collision:{wait_for_mouseup:!1},draggable:{items:".gs-w:not(.static)",distance:4,ignore_dragging:b.defaults.ignore_dragging.slice(0)},resize:{enabled:!1,axes:["both"],handle_append_to:"",handle_class:"gs-resize-handle",max_size:[1/0,1/0],min_size:[1,1]}};d.defaults=g,d.generated_stylesheets=[],d.sort_by_row_asc=function(b){return b=b.sort(function(b,c){return b.row||(b=a(b).coords().grid,c=a(c).coords().grid),b=e(b),c=e(c),b.row>c.row?1:-1})},d.sort_by_row_and_col_asc=function(a){return a=a.sort(function(a,b){return a=e(a),b=e(b),a.row>b.row||a.row===b.row&&a.col>b.col?1:-1})},d.sort_by_col_asc=function(a){return a=a.sort(function(a,b){return a=e(a),b=e(b),a.col>b.col?1:-1})},d.sort_by_row_desc=function(a){return a=a.sort(function(a,b){return a=e(a),b=e(b),a.row+a.size_yi&&this.add_faux_rows(Math.max(d-i,0));var k={col:j,row:g.row,size_x:c,size_y:d};return this.mutate_widget_in_gridmap(b,g,k),this.set_dom_grid_height(),this.set_dom_grid_width(),f&&f.call(this,k.size_x,k.size_y),b},h.collapse_widget=function(a,b){var c=a.coords().grid,d=parseInt(a.attr("pre_expand_sizex")),e=parseInt(a.attr("pre_expand_sizey")),f=parseInt(a.attr("pre_expand_col")),g={col:f,row:c.row,size_x:d,size_y:e};return this.mutate_widget_in_gridmap(a,c,g),this.set_dom_grid_height(),this.set_dom_grid_width(),b&&b.call(this,g.size_x,g.size_y),a},h.fit_to_content=function(a,b,c,d){var e=a.coords().grid,f=this.$wrapper.width(),g=this.$wrapper.height(),h=this.options.widget_base_dimensions[0]+2*this.options.widget_margins[0],i=this.options.widget_base_dimensions[1]+2*this.options.widget_margins[1],j=Math.ceil((f+2*this.options.widget_margins[0])/h),k=Math.ceil((g+2*this.options.widget_margins[1])/i),l={col:e.col,row:e.row,size_x:Math.min(b,j),size_y:Math.min(c,k)};return this.mutate_widget_in_gridmap(a,e,l),this.set_dom_grid_height(),this.set_dom_grid_width(),d&&d.call(this,l.size_x,l.size_y),a},h.center_widgets=debounce(function(){var b,c=this.$wrapper.width();b=this.is_responsive()?this.get_responsive_col_width():this.options.widget_base_dimensions[0]+2*this.options.widget_margins[0];var d=2*Math.floor(Math.max(Math.floor(c/b),this.min_col_count)/2);this.options.min_cols=d,this.options.max_cols=d,this.options.extra_cols=0,this.set_dom_grid_width(d),this.cols=d;var e=(d-this.prev_col_count)/2;return 0>e?(this.get_min_col()>-1*e?this.shift_cols(e):this.resize_widget_dimensions(this.options),setTimeout(a.proxy(function(){this.resize_widget_dimensions(this.options)},this),0)):e>0?(this.resize_widget_dimensions(this.options),setTimeout(a.proxy(function(){this.shift_cols(e)},this),0)):(this.resize_widget_dimensions(this.options),setTimeout(a.proxy(function(){this.resize_widget_dimensions(this.options)},this),0)),this.prev_col_count=d,this},200),h.get_min_col=function(){return Math.min.apply(Math,this.$widgets.map(a.proxy(function(b,c){return this.get_cells_occupied(a(c).coords().grid).cols},this)).get())},h.shift_cols=function(b){var c=this.$widgets.map(a.proxy(function(b,c){var d=a(c);return this.dom_to_coords(d)},this));c=d.sort_by_row_and_col_asc(c),c.each(a.proxy(function(c,d){var e=a(d.el),f=e.coords().grid,g=parseInt(e.attr("data-col")),h={col:Math.max(Math.round(g+b),1),row:f.row,size_x:f.size_x,size_y:f.size_y};setTimeout(a.proxy(function(){this.mutate_widget_in_gridmap(e,f,h)},this),0)},this))},h.mutate_widget_in_gridmap=function(b,c,d){var e=c.size_y,f=this.get_cells_occupied(c),g=this.get_cells_occupied(d),h=[];a.each(f.cols,function(b,c){-1===a.inArray(c,g.cols)&&h.push(c)});var i=[];a.each(g.cols,function(b,c){-1===a.inArray(c,f.cols)&&i.push(c)});var j=[];a.each(f.rows,function(b,c){-1===a.inArray(c,g.rows)&&j.push(c)});var k=[];if(a.each(g.rows,function(b,c){-1===a.inArray(c,f.rows)&&k.push(c)}),this.remove_from_gridmap(c),i.length){var l=[d.col,d.row,d.size_x,Math.min(e,d.size_y),b];this.empty_cells.apply(this,l)}if(k.length){var m=[d.col,d.row,d.size_x,d.size_y,b];this.empty_cells.apply(this,m)}if(c.col=d.col,c.row=d.row,c.size_x=d.size_x,c.size_y=d.size_y,this.add_to_gridmap(d,b),b.removeClass("player-revert"),this.update_widget_dimensions(b,d),h.length){var n=[h[0],d.row,h[h.length-1]-h[0]+1,Math.min(e,d.size_y),b];this.remove_empty_cells.apply(this,n)}if(j.length){var o=[d.col,d.row,d.size_x,d.size_y,b];this.remove_empty_cells.apply(this,o)}return this.move_widget_up(b),this},h.empty_cells=function(b,c,d,e,f){var g=this.widgets_below({col:b,row:c-e,size_x:d,size_y:e});return g.not(f).each(a.proxy(function(b,d){var f=a(d),g=f.coords().grid;if(g.row<=c+e-1){var h=c+e-g.row;this.move_widget_down(f,h)}},this)),this.is_resizing||this.set_dom_grid_height(),this},h.remove_empty_cells=function(b,c,d,e,f){var g=this.widgets_below({col:b,row:c,size_x:d,size_y:e});return g.not(f).each(a.proxy(function(b,c){this.move_widget_up(a(c),e)},this)),this.set_dom_grid_height(),this},h.next_position=function(a,b){a||(a=1),b||(b=1);for(var c,e=this.gridmap,f=e.length,g=[],h=1;f>h;h++){c=e[h].length;for(var i=1;c>=i;i++){var j=this.can_move_to({size_x:a,size_y:b},h,i);j&&g.push({col:h,row:i,size_y:b,size_x:a})}}return g.length?d.sort_by_row_and_col_asc(g)[0]:!1},h.remove_by_grid=function(a,b){var c=this.is_widget(a,b);c&&this.remove_widget(c)},h.remove_widget=function(b,c,d){var e=b instanceof a?b:a(b);if(0===e.length)return this;var f=e.coords().grid;if(void 0===f)return this;a.isFunction(c)&&(d=c,c=!1),this.cells_occupied_by_placeholder={},this.$widgets=this.$widgets.not(e);var g=this.widgets_below(e);return this.remove_from_gridmap(f),this.options.hide_element.call(this,e,a.proxy(function(){e.remove(),c||g.each(a.proxy(function(b,c){this.move_widget_up(a(c),f.size_y)},this)),this.set_dom_grid_height(),d&&d.call(this,b)},this)),this},h.remove_all_widgets=function(b){return this.$widgets.each(a.proxy(function(a,c){this.remove_widget(c,!0,b)},this)),this},h.serialize=function(b){b||(b=this.$widgets);var c=[];return b.each(a.proxy(function(b,d){var e=a(d);"undefined"!=typeof e.coords().grid&&c.push(this.options.serialize_params(e,e.coords().grid))},this)),c},h.serialize_changed=function(){return this.serialize(this.$changed)},h.dom_to_coords=function(a){return{col:parseInt(a.attr("data-col"),10),row:parseInt(a.attr("data-row"),10),size_x:parseInt(a.attr("data-sizex"),10)||1,size_y:parseInt(a.attr("data-sizey"),10)||1,max_size_x:parseInt(a.attr("data-max-sizex"),10)||!1,max_size_y:parseInt(a.attr("data-max-sizey"),10)||!1,min_size_x:parseInt(a.attr("data-min-sizex"),10)||!1,min_size_y:parseInt(a.attr("data-min-sizey"),10)||!1,el:a}},h.register_widget=function(b){var c=b instanceof a,d=c?this.dom_to_coords(b):b,e=!1;c||(b=d.el);var f=this.can_go_widget_up(d);return this.options.shift_widgets_up&&f&&(d.row=f,b.attr("data-row",f),this.$el.trigger("gridster:positionchanged",[d]),e=!0),this.options.avoid_overlapped_widgets&&!this.can_move_to({size_x:d.size_x,size_y:d.size_y},d.col,d.row)&&(a.extend(d,this.next_position(d.size_x,d.size_y)),b.attr({"data-col":d.col,"data-row":d.row,"data-sizex":d.size_x,"data-sizey":d.size_y}),e=!0),b.data("coords",b.coords()),b.data("coords").grid=d,this.add_to_gridmap(d,b),this.update_widget_dimensions(b,d),this.options.resize.enabled&&this.add_resize_handle(b),e},h.update_widget_position=function(a,b){return this.for_each_cell_occupied(a,function(a,c){return this.gridmap[a]?void(this.gridmap[a][c]=b):this}),this},h.update_widget_dimensions=function(a,b){var c=b.size_x*(this.is_responsive()?this.get_responsive_col_width():this.options.widget_base_dimensions[0])+(b.size_x-1)*this.options.widget_margins[0],d=b.size_y*this.options.widget_base_dimensions[1]+(b.size_y-1)*this.options.widget_margins[1];return a.data("coords").update({width:c,height:d}),a.attr({"data-col":b.col,"data-row":b.row,"data-sizex":b.size_x,"data-sizey":b.size_y}),this},h.update_widgets_dimensions=function(){return a.each(this.$widgets,a.proxy(function(b,c){var d=a(c).coords().grid;"object"==typeof d&&this.update_widget_dimensions(a(c),d)},this)),this},h.remove_from_gridmap=function(a){return this.update_widget_position(a,!1)},h.add_to_gridmap=function(a,b){this.update_widget_position(a,b||a.el)},h.draggable=function(){var b=this,c=a.extend(!0,{},this.options.draggable,{offset_left:this.options.widget_margins[0],offset_top:this.options.widget_margins[1],container_width:this.cols*this.min_widget_width+(this.cols+1)*this.options.widget_margins[0],limit:!0,start:function(c,d){b.$widgets.filter(".player-revert").removeClass("player-revert"),b.$player=a(this),b.$helper=a(d.$helper),b.helper=!b.$helper.is(b.$player),b.on_start_drag.call(b,c,d),b.$el.trigger("gridster:dragstart")},stop:function(a,c){b.on_stop_drag.call(b,a,c),b.$el.trigger("gridster:dragstop")},drag:throttle(function(a,c){b.on_drag.call(b,a,c),b.$el.trigger("gridster:drag")},60)});this.drag_api=this.$el.dragg(c).data("drag")},h.resizable=function(){return this.resize_api=this.$el.gridDraggable({items:"."+this.options.resize.handle_class,offset_left:this.options.widget_margins[0],container_width:this.container_width,move_element:!1,resize:!0,limit:this.options.max_cols!==1/0,scroll_container:this.options.scroll_container,start:a.proxy(this.on_start_resize,this),stop:a.proxy(function(b,c){delay(a.proxy(function(){this.on_stop_resize(b,c)},this),120)},this),drag:throttle(a.proxy(this.on_resize,this),60)}),this},h.setup_resize=function(){this.resize_handle_class=this.options.resize.handle_class;var b=this.options.resize.axes,c='';return this.resize_handle_tpl=a.map(b,function(a){return c.replace("{type}",a)}).join(""),a.isArray(this.options.draggable.ignore_dragging)&&this.options.draggable.ignore_dragging.push("."+this.resize_handle_class),this},h.on_start_drag=function(b,c){this.$helper.add(this.$player).add(this.$wrapper).addClass("dragging"),this.highest_col=this.get_highest_occupied_cell().col,this.$player.addClass("player"),this.player_grid_data=this.$player.coords().grid,this.placeholder_grid_data=a.extend({},this.player_grid_data),this.set_dom_grid_height(this.$el.height()+this.player_grid_data.size_y*this.min_widget_height),this.set_dom_grid_width(this.cols);var d=this.player_grid_data.size_x,e=this.cols-this.highest_col;this.options.max_cols===1/0&&d>=e&&this.add_faux_cols(Math.min(d-e,1));var f=this.faux_grid,g=this.$player.data("coords").coords;this.cells_occupied_by_player=this.get_cells_occupied(this.player_grid_data),this.cells_occupied_by_placeholder=this.get_cells_occupied(this.placeholder_grid_data),this.last_cols=[],this.last_rows=[],this.collision_api=this.$helper.collision(f,this.options.collision),this.$preview_holder=a("<"+this.$player.get(0).tagName+" />",{"class":"preview-holder","data-row":this.$player.attr("data-row"),"data-col":this.$player.attr("data-col"),css:{width:g.width,height:g.height}}).appendTo(this.$el),this.options.draggable.start&&this.options.draggable.start.call(this,b,c)},h.on_drag=function(a,b){if(null===this.$player)return!1;var c=this.options.widget_margins,d=this.$preview_holder.attr("data-col"),e=this.$preview_holder.attr("data-row"),f={left:b.position.left+this.baseX-c[0]*d,top:b.position.top+this.baseY-c[1]*e};if(this.options.max_cols===1/0){var g=this.placeholder_grid_data.col+this.placeholder_grid_data.size_x-1;g>=this.cols-1&&this.options.max_cols>=this.cols+1&&(this.add_faux_cols(1),this.set_dom_grid_width(this.cols+1),this.drag_api.set_limits(this.cols*this.min_widget_width+(this.cols+1)*this.options.widget_margins[0])),this.collision_api.set_colliders(this.faux_grid)}this.colliders_data=this.collision_api.get_closest_colliders(f),this.on_overlapped_column_change(this.on_start_overlapping_column,this.on_stop_overlapping_column),this.on_overlapped_row_change(this.on_start_overlapping_row,this.on_stop_overlapping_row),this.helper&&this.$player&&this.$player.css({left:b.position.left,top:b.position.top}),this.options.draggable.drag&&this.options.draggable.drag.call(this,a,b)},h.on_stop_drag=function(a,b){this.$helper.add(this.$player).add(this.$wrapper).removeClass("dragging");var c=this.options.widget_margins,d=this.$preview_holder.attr("data-col"),e=this.$preview_holder.attr("data-row");b.position.left=b.position.left+this.baseX-c[0]*d,b.position.top=b.position.top+this.baseY-c[1]*e,this.colliders_data=this.collision_api.get_closest_colliders(b.position),this.on_overlapped_column_change(this.on_start_overlapping_column,this.on_stop_overlapping_column),this.on_overlapped_row_change(this.on_start_overlapping_row,this.on_stop_overlapping_row),this.$changed=this.$changed.add(this.$player),this.placeholder_grid_data.el.coords().grid.col===this.placeholder_grid_data.col&&this.placeholder_grid_data.el.coords().grid.row===this.placeholder_grid_data.row||(this.update_widget_position(this.placeholder_grid_data.el.coords().grid,!1),this.options.collision.wait_for_mouseup&&this.for_each_cell_occupied(this.placeholder_grid_data,function(a,b){if(this.is_widget(a,b)){var c=this.placeholder_grid_data.row+this.placeholder_grid_data.size_y,d=parseInt(this.gridmap[a][b][0].getAttribute("data-row")),e=c-d;this.move_widget_down(this.is_widget(a,b),e)}})),this.cells_occupied_by_player=this.get_cells_occupied(this.placeholder_grid_data);var f=this.placeholder_grid_data.col,g=this.placeholder_grid_data.row;this.set_cells_player_occupies(f,g),this.$player.coords().grid.row=g,this.$player.coords().grid.col=f,this.$player.addClass("player-revert").removeClass("player").attr({"data-col":f,"data-row":g}).css({left:"",top:""}),this.options.draggable.stop&&this.options.draggable.stop.call(this,a,b),this.$preview_holder.remove(),this.$player=null,this.$helper=null,this.placeholder_grid_data={},this.player_grid_data={},this.cells_occupied_by_placeholder={},this.cells_occupied_by_player={},this.w_queue={},this.set_dom_grid_height(),this.set_dom_grid_width(),this.options.max_cols===1/0&&this.drag_api.set_limits(this.cols*this.min_widget_width+(this.cols+1)*this.options.widget_margins[0])},h.on_start_resize=function(b,c){this.$resized_widget=c.$player.closest(".gs-w"),this.resize_coords=this.$resized_widget.coords(),this.resize_wgd=this.resize_coords.grid,this.resize_initial_width=this.resize_coords.coords.width,this.resize_initial_height=this.resize_coords.coords.height,this.resize_initial_sizex=this.resize_coords.grid.size_x,this.resize_initial_sizey=this.resize_coords.grid.size_y,this.resize_initial_col=this.resize_coords.grid.col,this.resize_last_sizex=this.resize_initial_sizex,this.resize_last_sizey=this.resize_initial_sizey,this.resize_max_size_x=Math.min(this.resize_wgd.max_size_x||this.options.resize.max_size[0],this.options.max_cols-this.resize_initial_col+1),this.resize_max_size_y=this.resize_wgd.max_size_y||this.options.resize.max_size[1],this.resize_min_size_x=this.resize_wgd.min_size_x||this.options.resize.min_size[0]||1,this.resize_min_size_y=this.resize_wgd.min_size_y||this.options.resize.min_size[1]||1,this.resize_initial_last_col=this.get_highest_occupied_cell().col,this.set_dom_grid_width(this.cols),this.resize_dir={right:c.$player.is("."+this.resize_handle_class+"-x"),bottom:c.$player.is("."+this.resize_handle_class+"-y")},this.is_responsive()||this.$resized_widget.css({"min-width":this.options.widget_base_dimensions[0],"min-height":this.options.widget_base_dimensions[1]});var d=this.$resized_widget.get(0).tagName;this.$resize_preview_holder=a("<"+d+" />",{"class":"preview-holder resize-preview-holder","data-row":this.$resized_widget.attr("data-row"),"data-col":this.$resized_widget.attr("data-col"), -css:{width:this.resize_initial_width,height:this.resize_initial_height}}).appendTo(this.$el),this.$resized_widget.addClass("resizing"),this.options.resize.start&&this.options.resize.start.call(this,b,c,this.$resized_widget),this.$el.trigger("gridster:resizestart")},h.on_stop_resize=function(b,c){this.$resized_widget.removeClass("resizing").css({width:"",height:"","min-width":"","min-height":""}),delay(a.proxy(function(){this.$resize_preview_holder.remove().css({"min-width":"","min-height":""}),this.options.resize.stop&&this.options.resize.stop.call(this,b,c,this.$resized_widget),this.$el.trigger("gridster:resizestop")},this),300),this.set_dom_grid_width(),this.set_dom_grid_height(),this.options.max_cols===1/0&&this.drag_api.set_limits(this.cols*this.min_widget_width)},h.on_resize=function(a,b){var c,d=b.pointer.diff_left,e=b.pointer.diff_top,f=this.is_responsive()?this.get_responsive_col_width():this.options.widget_base_dimensions[0],g=this.options.widget_base_dimensions[1],h=this.options.widget_margins[0],i=this.options.widget_margins[1],j=this.resize_max_size_x,k=this.resize_min_size_x,l=this.resize_max_size_y,m=this.resize_min_size_y,n=this.options.max_cols===1/0,o=Math.ceil(d/(f+2*h)-.2),p=Math.ceil(e/(g+2*i)-.2),q=Math.max(1,this.resize_initial_sizex+o),r=Math.max(1,this.resize_initial_sizey+p),s=Math.floor(this.container_width/this.min_widget_width-this.resize_initial_col+1),t=s*this.min_widget_width+(s-1)*h;q=Math.max(Math.min(q,j),k),q=Math.min(s,q),c=j*f+(q-1)*h;var u=Math.min(c,t),v=k*f+(q-1)*h;r=Math.max(Math.min(r,l),m);var w=l*g+(r-1)*i,x=m*g+(r-1)*i;if(this.resize_dir.right?r=this.resize_initial_sizey:this.resize_dir.bottom&&(q=this.resize_initial_sizex),n){var y=this.resize_initial_col+q-1;n&&this.resize_initial_last_col<=y&&(this.set_dom_grid_width(Math.max(y+1,this.cols)),this.colsd;d++)-1===a.inArray(e[d],this.last_cols)&&(b||a.noop).call(this,e[d]);for(d=0;f>d;d++)-1===a.inArray(this.last_cols[d],e)&&(c||a.noop).call(this,this.last_cols[d]);return this.last_cols=e,this},h.on_overlapped_row_change=function(b,c){if(!this.colliders_data.length)return this;var d,e=this.get_targeted_rows(this.colliders_data[0].el.data.row),f=this.last_rows.length,g=e.length;for(d=0;g>d;d++)-1===a.inArray(e[d],this.last_rows)&&(b||a.noop).call(this,e[d]);for(d=0;f>d;d++)-1===a.inArray(this.last_rows[d],e)&&(c||a.noop).call(this,this.last_rows[d]);this.last_rows=e},h.set_player=function(b,c,d){var e=this,f=!1,g=d?{col:b}:e.colliders_data[0].el.data,h=g.col,i=g.row||c;this.player_grid_data={col:h,row:i,size_y:this.player_grid_data.size_y,size_x:this.player_grid_data.size_x},this.cells_occupied_by_player=this.get_cells_occupied(this.player_grid_data),this.cells_occupied_by_placeholder=this.get_cells_occupied(this.placeholder_grid_data);var j=this.get_widgets_overlapped(this.player_grid_data),k=this.player_grid_data.size_y,l=this.player_grid_data.size_x,m=this.cells_occupied_by_placeholder,n=this;if(j.each(a.proxy(function(b,c){var d=a(c),e=d.coords().grid,g=m.cols[0]+l-1,o=m.rows[0]+k-1;if(d.hasClass(n.options.static_class))return!0;if(n.options.collision.wait_for_mouseup&&n.drag_api.is_dragging)n.placeholder_grid_data.col=h,n.placeholder_grid_data.row=i,n.cells_occupied_by_placeholder=n.get_cells_occupied(n.placeholder_grid_data),n.$preview_holder.attr({"data-row":i,"data-col":h});else if(e.size_x<=l&&e.size_y<=k)if(n.is_swap_occupied(m.cols[0],e.row,e.size_x,e.size_y)||n.is_player_in(m.cols[0],e.row)||n.is_in_queue(m.cols[0],e.row,d))if(n.is_swap_occupied(g,e.row,e.size_x,e.size_y)||n.is_player_in(g,e.row)||n.is_in_queue(g,e.row,d))if(n.is_swap_occupied(e.col,m.rows[0],e.size_x,e.size_y)||n.is_player_in(e.col,m.rows[0])||n.is_in_queue(e.col,m.rows[0],d))if(n.is_swap_occupied(e.col,o,e.size_x,e.size_y)||n.is_player_in(e.col,o)||n.is_in_queue(e.col,o,d))if(n.is_swap_occupied(m.cols[0],m.rows[0],e.size_x,e.size_y)||n.is_player_in(m.cols[0],m.rows[0])||n.is_in_queue(m.cols[0],m.rows[0],d))for(var p=0;l>p;p++)for(var q=0;k>q;q++){var r=m.cols[0]+p,s=m.rows[0]+q;if(!n.is_swap_occupied(r,s,e.size_x,e.size_y)&&!n.is_player_in(r,s)&&!n.is_in_queue(r,s,d)){f=n.queue_widget(r,s,d),p=l;break}}else n.options.move_widgets_down_only?j.each(a.proxy(function(b,c){var d=a(c);n.can_go_down(d)&&d.coords().grid.row===n.player_grid_data.row&&!n.is_in_queue(g,e.row,d)&&(n.move_widget_down(d,n.player_grid_data.size_y),n.set_placeholder(h,i))})):f=n.queue_widget(m.cols[0],m.rows[0],d);else f=n.queue_widget(e.col,o,d);else f=n.queue_widget(e.col,m.rows[0],d);else f=n.queue_widget(g,e.row,d);else n.options.move_widgets_down_only?j.each(a.proxy(function(b,c){var d=a(c);n.can_go_down(d)&&d.coords().grid.row===n.player_grid_data.row&&!n.is_in_queue(d.coords().grid.col,e.row,d)&&(n.move_widget_down(d,n.player_grid_data.size_y),n.set_placeholder(h,i))})):f=n.queue_widget(m.cols[0],e.row,d);else n.options.shift_larger_widgets_down&&!f&&j.each(a.proxy(function(b,c){var d=a(c);n.can_go_down(d)&&d.coords().grid.row===n.player_grid_data.row&&(n.move_widget_down(d,n.player_grid_data.size_y),n.set_placeholder(h,i))}));n.clean_up_changed()})),f&&this.can_placeholder_be_set(h,i,l,k)){for(var o in this.w_queue){var p=parseInt(o.split("_")[0]),q=parseInt(o.split("_")[1]);"full"!==this.w_queue[o]&&this.new_move_widget_to(this.w_queue[o],p,q)}this.set_placeholder(h,i)}if(!j.length){if(this.options.shift_widgets_up){var r=this.can_go_player_up(this.player_grid_data);r!==!1&&(i=r)}this.can_placeholder_be_set(h,i,l,k)&&this.set_placeholder(h,i)}return this.w_queue={},{col:h,row:i}},h.is_swap_occupied=function(a,b,c,d){for(var e=!1,f=0;c>f;f++)for(var g=0;d>g;g++){var h=a+f,i=b+g,j=h+"_"+i;if(this.is_occupied(h,i))e=!0;else if(j in this.w_queue){if("full"===this.w_queue[j]){e=!0;continue}var k=this.w_queue[j],l=k.coords().grid;this.is_widget_under_player(l.col,l.row)||delete this.w_queue[j]}i>parseInt(this.options.max_rows)&&(e=!0),h>parseInt(this.options.max_cols)&&(e=!0),this.is_player_in(h,i)&&(e=!0)}return e},h.can_placeholder_be_set=function(a,b,c,d){for(var e=!0,f=0;c>f;f++)for(var g=0;d>g;g++){var h=a+f,i=b+g,j=this.is_widget(h,i);i>parseInt(this.options.max_rows)&&(e=!1),h>parseInt(this.options.max_cols)&&(e=!1),this.is_occupied(h,i)&&!this.is_widget_queued_and_can_move(j)&&(e=!1)}return e},h.queue_widget=function(a,b,c){var d=c,e=d.coords().grid,f=a+"_"+b;if(f in this.w_queue)return!1;this.w_queue[f]=d;for(var g=0;g=0&&a.inArray(c,d.rows)>=0},h.is_placeholder_in=function(b,c){var d=this.cells_occupied_by_placeholder||{};return this.is_placeholder_in_col(b)&&a.inArray(c,d.rows)>=0},h.is_placeholder_in_col=function(b){var c=this.cells_occupied_by_placeholder||[];return a.inArray(b,c.cols)>=0},h.is_empty=function(a,b){return"undefined"!=typeof this.gridmap[a]?"undefined"!=typeof this.gridmap[a][b]&&this.gridmap[a][b]===!1:!0},h.is_valid_col=function(a,b){return this.options.max_cols===1/0?!0:this.cols>=this.calculate_highest_col(a,b)},h.is_valid_row=function(a,b){return this.rows>=this.calculate_highest_row(a,b)},h.calculate_highest_col=function(a,b){return a+(b||1)-1},h.calculate_highest_row=function(a,b){return a+(b||1)-1},h.is_occupied=function(a,b){return this.gridmap[a]?!!this.gridmap[a][b]:!1},h.is_widget=function(a,b){var c=this.gridmap[a];return c?(c=c[b],c?c:!1):!1},h.is_static=function(a,b){var c=this.gridmap[a];return c?(c=c[b],!(!c||!c.hasClass(this.options.static_class))):!1},h.is_widget_under_player=function(a,b){return this.is_widget(a,b)?this.is_player_in(a,b):!1},h.get_widgets_under_player=function(b){b||(b=this.cells_occupied_by_player||{cols:[],rows:[]});var c=a([]);return a.each(b.cols,a.proxy(function(d,e){a.each(b.rows,a.proxy(function(a,b){this.is_widget(e,b)&&(c=c.add(this.gridmap[e][b]))},this))},this)),c},h.set_placeholder=function(b,c){var d=a.extend({},this.placeholder_grid_data),e=b+d.size_x-1;e>this.cols&&(b-=e-b);var f=this.placeholder_grid_data.row0&&(this.is_empty(a,h)||this.is_player(a,h)||this.is_widget(a,h)&&g[h].is(f));)d[a].push(h),e=e>h?h:e;return 0===d[a].length?(c=!1,!0):void d[a].sort(function(a,b){return a-b})}),c?this.get_valid_rows(a,d,e):!1},h.can_go_widget_up=function(a){var b=a.row+a.size_y-1,c=!0,d=[],e=1e4;return this.for_each_column_occupied(a,function(f){var g=this.gridmap[f];d[f]=[];for(var h=b+1;--h>0&&(!this.is_widget(f,h)||this.is_player_in(f,h)||g[h].is(a.el));)this.is_player(f,h)||this.is_placeholder_in(f,h)||this.is_player_in(f,h)||d[f].push(h),e>h&&(e=h);return 0===d[f].length?(c=!1,!0):void d[f].sort(function(a,b){return a-b})}),c?this.get_valid_rows(a,d,e):!1},h.get_valid_rows=function(b,c,d){for(var e=b.row,f=b.row+b.size_y-1,g=b.size_y,h=d-1,i=[];++h<=f;){var j=!0;if(a.each(c,function(b,c){a.isArray(c)&&-1===a.inArray(h,c)&&(j=!1)}),j===!0&&(i.push(h),i.length===g))break}var k=!1;return 1===g?i[0]!==e&&(k=i[0]||!1):i[0]!==e&&(k=this.get_consecutive_numbers_index(i,g)),k},h.get_consecutive_numbers_index=function(a,b){for(var c=a.length,d=[],e=!0,f=-1,g=0;c>g;g++){if(e||a[g]===f+1){if(d.push(g),d.length===b)break;e=!1}else d=[],e=!0;f=a[g]}return d.length>=b?a[d[0]]:!1},h.get_widgets_overlapped=function(){var b=a([]),c=[],d=this.cells_occupied_by_player.rows.slice(0);return d.reverse(),a.each(this.cells_occupied_by_player.cols,a.proxy(function(e,f){a.each(d,a.proxy(function(d,e){if(!this.gridmap[f])return!0;var g=this.gridmap[f][e];this.is_occupied(f,e)&&!this.is_player(g)&&-1===a.inArray(g,c)&&(b=b.add(g),c.push(g))},this))},this)),b},h.on_start_overlapping_column=function(a){this.set_player(a,void 0,!1)},h.on_start_overlapping_row=function(a){this.set_player(void 0,a,!1)},h.on_stop_overlapping_column=function(a){var b=this;this.options.shift_larger_widgets_down&&this.for_each_widget_below(a,this.cells_occupied_by_player.rows[0],function(a,c){b.move_widget_up(this,b.player_grid_data.size_y)})},h.on_stop_overlapping_row=function(a){var b=this,c=this.cells_occupied_by_player.cols;if(this.options.shift_larger_widgets_down)for(var d=0,e=c.length;e>d;d++)this.for_each_widget_below(c[d],a,function(a,c){b.move_widget_up(this,b.player_grid_data.size_y)})},h.new_move_widget_to=function(a,b,c){var d=a.coords().grid;return this.remove_from_gridmap(d),d.row=c,d.col=b,this.add_to_gridmap(d),a.attr("data-row",c),a.attr("data-col",b),this.update_widget_position(d,a),this.$changed=this.$changed.add(a),this},h.move_widget=function(a,b,c,d){var e=a.coords().grid,f={col:b,row:c,size_x:e.size_x,size_y:e.size_y};return this.mutate_widget_in_gridmap(a,e,f),this.set_dom_grid_height(),this.set_dom_grid_width(),d&&d.call(this,f.col,f.row),a},h.move_widget_to=function(b,c){var d=this,e=b.coords().grid,f=this.widgets_below(b),g=this.can_move_to(e,e.col,c);return g===!1?!1:(this.remove_from_gridmap(e),e.row=c,this.add_to_gridmap(e),b.attr("data-row",c),this.$changed=this.$changed.add(b),f.each(function(b,c){var e=a(c),f=e.coords().grid,g=d.can_go_widget_up(f);g&&g!==f.row&&d.move_widget_to(e,g)}),this)},h.move_widget_up=function(b,c){if(void 0===c)return!1;var d=b.coords().grid,e=d.row,f=[];return c||(c=1),this.can_go_up(b)?void this.for_each_column_occupied(d,function(d){if(-1===a.inArray(b,f)){var g=b.coords().grid,h=e-c;if(h=this.can_go_up_to_row(g,d,h),!h)return!0;this.remove_from_gridmap(g),g.row=h,this.add_to_gridmap(g),b.attr("data-row",g.row),this.$changed=this.$changed.add(b),f.push(b)}}):!1},h.move_widget_down=function(b,c){var d,e,f,g;if(0>=c)return!1;if(d=b.coords().grid,e=d.row,f=[],g=c,!b)return!1;if(-1===a.inArray(b,f)){var h=b.coords().grid,i=e+c,j=this.widgets_below(b);this.remove_from_gridmap(h),j.each(a.proxy(function(b,c){var d=a(c),e=d.coords().grid,f=this.displacement_diff(e,h,g);f>0&&this.move_widget_down(d,f)},this)),h.row=i,this.update_widget_position(h,b),b.attr("data-row",h.row),this.$changed=this.$changed.add(b),f.push(b)}},h.can_go_up_to_row=function(b,c,d){var e,f=!0,g=[],h=b.row;if(this.for_each_column_occupied(b,function(a){for(g[a]=[],e=h;e--&&this.is_empty(a,e)&&!this.is_placeholder_in(a,e);)g[a].push(e);return g[a].length?void 0:(f=!1,!0)}),!f)return!1;for(e=d,e=1;h>e;e++){for(var i=!0,j=0,k=g.length;k>j;j++)g[j]&&-1===a.inArray(e,g[j])&&(i=!1);if(i===!0){f=e;break}}return f},h.displacement_diff=function(a,b,c){var d=a.row,e=[],f=b.row+b.size_y;this.for_each_column_occupied(a,function(a){for(var b=0,c=f;d>c;c++)this.is_empty(a,c)&&(b+=1);e.push(b)});var g=Math.max.apply(Math,e);return c-=g,c>0?c:0},h.widgets_below=function(b){var c=a([]),e=a.isPlainObject(b)?b:b.coords().grid;if(void 0===e)return c;var f=this,g=e.row+e.size_y-1;return this.for_each_column_occupied(e,function(b){f.for_each_widget_below(b,g,function(b,d){return f.is_player(this)||-1!==a.inArray(this,c)?void 0:(c=c.add(this),!0)})}),d.sort_by_row_asc(c)},h.set_cells_player_occupies=function(a,b){return this.remove_from_gridmap(this.placeholder_grid_data),this.placeholder_grid_data.col=a,this.placeholder_grid_data.row=b,this.add_to_gridmap(this.placeholder_grid_data,this.$player),this},h.empty_cells_player_occupies=function(){return this.remove_from_gridmap(this.placeholder_grid_data),this},h.can_go_down=function(b){var c=!0,d=this;return b.hasClass(this.options.static_class)&&(c=!1),this.widgets_below(b).each(function(){a(this).hasClass(d.options.static_class)&&(c=!1)}),c},h.can_go_up=function(a){var b=a.coords().grid,c=b.row,d=c-1,e=!0;return 1===c?!1:(this.for_each_column_occupied(b,function(a){return this.is_occupied(a,d)||this.is_player(a,d)||this.is_placeholder_in(a,d)||this.is_player_in(a,d)?(e=!1,!0):void 0}),e)},h.can_move_to=function(a,b,c){var d=a.el,e={size_y:a.size_y,size_x:a.size_x,col:b,row:c},f=!0;if(this.options.max_cols!==1/0){var g=b+a.size_x-1;if(g>this.cols)return!1}return this.options.max_rows=d;d++)c.push(d);return c},h.get_targeted_rows=function(a){for(var b=(a||this.player_grid_data.row)+(this.player_grid_data.size_y-1),c=[],d=a;b>=d;d++)c.push(d);return c},h.get_cells_occupied=function(b){var c,d={cols:[],rows:[]};for(arguments[1]instanceof a&&(b=arguments[1].coords().grid),c=0;c0&&this.is_widget(d,m)&&-1===a.inArray(g[d][m],l)&&(h=f.call(g[d][m],d,m),l.push(g[d][m]),h)););},"for_each/below":function(){for(m=e+1,i=g[d].length;i>m;m++)this.is_widget(d,m)&&-1===a.inArray(g[d][m],l)&&(h=f.call(g[d][m],d,m),l.push(g[d][m]))}};n[j]&&n[j].call(this)}},h.for_each_widget_above=function(a,b,c){return this._traversing_widgets("for_each","above",a,b,c),this},h.for_each_widget_below=function(a,b,c){return this._traversing_widgets("for_each","below",a,b,c),this},h.get_highest_occupied_cell=function(){for(var a,b=this.gridmap,c=b[1].length,d=[],e=[],f=b.length-1;f>=1;f--)for(a=c-1;a>=1;a--)if(this.is_widget(f,a)){d.push(a),e.push(f);break}return{col:Math.max.apply(Math,e),row:Math.max.apply(Math,d)}},h.get_widgets_in_range=function(b,c,d,e){var f,g,h,i,j=a([]);for(f=d;f>=b;f--)for(g=e;g>=c;g--)h=this.is_widget(f,g),h!==!1&&(i=h.data("coords").grid,i.col>=b&&i.col<=d&&i.row>=c&&i.row<=e&&(j=j.add(h)));return j},h.get_widgets_at_cell=function(a,b){return this.get_widgets_in_range(a,b,a,b)},h.get_widgets_from=function(b,c){var d=a();return b&&(d=d.add(this.$widgets.filter(function(){var c=parseInt(a(this).attr("data-col"));return c===b||c>b}))),c&&(d=d.add(this.$widgets.filter(function(){var b=parseInt(a(this).attr("data-row"));return b===c||b>c}))),d},h.set_dom_grid_height=function(a){if("undefined"==typeof a){var b=this.get_highest_occupied_cell().row;a=(b+1)*this.options.widget_margins[1]+b*this.min_widget_height}return this.container_height=a,this.$el.css("height",this.container_height),this},h.set_dom_grid_width=function(a){"undefined"==typeof a&&(a=this.get_highest_occupied_cell().col);var b=this.options.max_cols===1/0?this.options.max_cols:this.cols;return a=Math.min(b,Math.max(a,this.options.min_cols)),this.container_width=(a+1)*this.options.widget_margins[0]+a*this.min_widget_width,this.is_responsive()?(this.$el.css({"min-width":"100%","max-width":"100%"}),this):(this.$el.css("width",this.container_width),this)},h.is_responsive=function(){return this.options.autogenerate_stylesheet&&"auto"===this.options.widget_base_dimensions[0]&&this.options.max_cols!==1/0},h.get_responsive_col_width=function(){var a=this.cols||this.options.max_cols;return(this.$el[0].clientWidth-3-(a+1)*this.options.widget_margins[0])/a},h.resize_responsive_layout=function(){return this.min_widget_width=this.get_responsive_col_width(),this.generate_stylesheet(),this.update_widgets_dimensions(),this.drag_api.set_limits(this.cols*this.min_widget_width+(this.cols+1)*this.options.widget_margins[0]),this},h.toggle_collapsed_grid=function(a,b){return a?(this.$widgets.css({"margin-top":b.widget_margins[0],"margin-bottom":b.widget_margins[0],"min-height":b.widget_base_dimensions[1]}),this.$el.addClass("collapsed"),this.resize_api&&this.disable_resize(),this.drag_api&&this.disable()):(this.$widgets.css({"margin-top":"auto","margin-bottom":"auto","min-height":"auto"}),this.$el.removeClass("collapsed"),this.resize_api&&this.enable_resize(),this.drag_api&&this.enable()),this},h.generate_stylesheet=function(b){var c,e="",f=this.is_responsive()&&this.options.responsive_breakpoint&&a(window).width()=0)return!1;for(this.generated_stylesheets.push(g),d.generated_stylesheets.push(g),c=1;c<=b.cols+1;c++)e+=b.namespace+' [data-col="'+c+'"] { left:'+(f?this.options.widget_margins[0]:c*b.widget_margins[0]+(c-1)*b.widget_base_dimensions[0])+"px; }\n";for(c=1;c<=b.rows+1;c++)e+=b.namespace+' [data-row="'+c+'"] { top:'+(c*b.widget_margins[1]+(c-1)*b.widget_base_dimensions[1])+"px; }\n";for(var h=1;h<=b.rows;h++)e+=b.namespace+' [data-sizey="'+h+'"] { height:'+(f?"auto":h*b.widget_base_dimensions[1]+(h-1)*b.widget_margins[1])+(f?"":"px")+"; }\n";for(var i=1;i<=b.cols;i++){var j=i*b.widget_base_dimensions[0]+(i-1)*b.widget_margins[0];e+=b.namespace+' [data-sizex="'+i+'"] { width:'+(f?this.$wrapper.width()-2*this.options.widget_margins[0]:j>this.$wrapper.width()?this.$wrapper.width():j)+"px; }\n"}return this.remove_style_tags(),this.add_style_tag(e)},h.add_style_tag=function(a){var b=document,c="gridster-stylesheet";if(""!==this.options.namespace&&(c=c+"-"+this.options.namespace),!document.getElementById(c)){var d=b.createElement("style");d.id=c,b.getElementsByTagName("head")[0].appendChild(d),d.setAttribute("type","text/css"),d.styleSheet?d.styleSheet.cssText=a:d.appendChild(document.createTextNode(a)),this.remove_style_tags(),this.$style_tags=this.$style_tags.add(d)}return this},h.remove_style_tags=function(){var b=d.generated_stylesheets,c=this.generated_stylesheets;this.$style_tags.remove(),d.generated_stylesheets=a.map(b,function(b){return-1===a.inArray(b,c)?b:void 0})},h.generate_faux_grid=function(a,b){this.faux_grid=[],this.gridmap=[];var c,d;for(c=b;c>0;c--)for(this.gridmap[c]=[],d=a;d>0;d--)this.add_faux_cell(d,c);return this},h.add_faux_cell=function(b,c){var d=a({left:this.baseX+(c-1)*this.min_widget_width,top:this.baseY+(b-1)*this.min_widget_height,width:this.min_widget_width,height:this.min_widget_height,col:c,row:b,original_col:c,original_row:b}).coords();return a.isArray(this.gridmap[c])||(this.gridmap[c]=[]),"undefined"==typeof this.gridmap[c][b]&&(this.gridmap[c][b]=!1),this.faux_grid.push(d),this},h.add_faux_rows=function(a){a=window.parseInt(a,10);for(var b=this.rows,c=b+parseInt(a||1),d=c;d>b;d--)for(var e=this.cols;e>=1;e--)this.add_faux_cell(d,e);return this.rows=c,this.options.autogenerate_stylesheet&&this.generate_stylesheet(),this},h.add_faux_cols=function(a){a=window.parseInt(a,10);var b=this.cols,c=b+parseInt(a||1);c=Math.min(c,this.options.max_cols);for(var d=b+1;c>=d;d++)for(var e=this.rows;e>=1;e--)this.add_faux_cell(e,d);return this.cols=c,this.options.autogenerate_stylesheet&&this.generate_stylesheet(),this},h.recalculate_faux_grid=function(){var b=this.$wrapper.width();return this.baseX=(f.width()-b)/2,this.baseY=this.$wrapper.offset().top,"relative"===this.$wrapper.css("position")&&(this.baseX=this.baseY=0),a.each(this.faux_grid,a.proxy(function(a,b){this.faux_grid[a]=b.update({left:this.baseX+(b.data.col-1)*this.min_widget_width,top:this.baseY+(b.data.row-1)*this.min_widget_height})},this)),this.is_responsive()&&this.resize_responsive_layout(),this.options.center_widgets&&this.center_widgets(),this},h.resize_widget_dimensions=function(b){return b.widget_margins&&(this.options.widget_margins=b.widget_margins),b.widget_base_dimensions&&(this.options.widget_base_dimensions=b.widget_base_dimensions),this.min_widget_width=2*this.options.widget_margins[0]+this.options.widget_base_dimensions[0],this.min_widget_height=2*this.options.widget_margins[1]+this.options.widget_base_dimensions[1],this.$widgets.each(a.proxy(function(b,c){var d=a(c);this.resize_widget(d)},this)),this.generate_grid_and_stylesheet(),this.get_widgets_from_DOM(),this.set_dom_grid_height(),this.set_dom_grid_width(),this},h.get_widgets_from_DOM=function(){var b=this.$widgets.map(a.proxy(function(b,c){var d=a(c);return this.dom_to_coords(d)},this));b=d.sort_by_row_and_col_asc(b);var c=a(b).map(a.proxy(function(a,b){return this.register_widget(b)||null},this));return c.length&&this.$el.trigger("gridster:positionschanged"),this},h.get_num_widgets=function(){return this.$widgets.size()},h.set_num_columns=function(b){var c=this.options.max_cols,d=Math.floor(b/(this.min_widget_width+this.options.widget_margins[0]))+this.options.extra_cols,e=this.$widgets.map(function(){return a(this).attr("data-col")}).get();e.length||(e=[0]);var f=Math.max.apply(Math,e);this.cols=Math.max(f,d,this.options.min_cols),c!==1/0&&c>=f&&cNov 2020 - - - - diff --git a/frontend/favicon.ico b/frontend/favicon.ico deleted file mode 100755 index 035bf97..0000000 Binary files a/frontend/favicon.ico and /dev/null differ diff --git a/frontend/index.html b/frontend/index.html deleted file mode 100644 index f3caf3f..0000000 --- a/frontend/index.html +++ /dev/null @@ -1,486 +0,0 @@ - - - - - - - - Placeholder - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Skip to main content - - - - - - -
-
- -
-
- - - - - - - -
-
-
    -
-
-
-
-

Developed with funding from the Vehicle Technologies Office, Office of Energy Efficiency and Renewable Energy, U.S. Department of Energy.

-
-
- U.S. Department of Energy Office of Energy Efficiency and Renewable Energy -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/metrics_program.html b/frontend/metrics_program.html deleted file mode 100644 index 7574620..0000000 --- a/frontend/metrics_program.html +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/metrics_study.html b/frontend/metrics_study.html deleted file mode 100644 index 9657483..0000000 --- a/frontend/metrics_study.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/frontend/options.html b/frontend/options.html deleted file mode 100644 index 5c08b8c..0000000 --- a/frontend/options.html +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/programs.html b/frontend/programs.html deleted file mode 100644 index b78603b..0000000 --- a/frontend/programs.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/outputs/README.md b/outputs/README.md new file mode 100644 index 0000000..bd2374a --- /dev/null +++ b/outputs/README.md @@ -0,0 +1 @@ +Placeholder directory to hold outputs \ No newline at end of file diff --git a/viz_scripts/docker/.DS_Store b/rm_src/.DS_Store similarity index 79% rename from viz_scripts/docker/.DS_Store rename to rm_src/.DS_Store index c95f7de..e6699c1 100644 Binary files a/viz_scripts/docker/.DS_Store and b/rm_src/.DS_Store differ diff --git a/rm_src/01_extract_db_data.ipynb b/rm_src/01_extract_db_data.ipynb new file mode 100644 index 0000000..619373b --- /dev/null +++ b/rm_src/01_extract_db_data.ipynb @@ -0,0 +1,1672 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "38b147ff", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import ast\n", + "import sys\n", + "import pickle\n", + "import importlib\n", + "\n", + "import matplotlib.pyplot as plt\n", + "\n", + "import numpy as np\n", + "import pandas as pd\n", + "import seaborn as sns\n", + "\n", + "from pandas.api.types import is_string_dtype\n", + "\n", + "from pathlib import Path\n", + "from uuid import UUID\n", + "from collections import defaultdict\n", + "\n", + "pd.set_option(\"display.max_columns\", 100)\n", + "\n", + "%matplotlib inline" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "e550aa2b", + "metadata": {}, + "outputs": [], + "source": [ + "INCLUDE_TEST_USERS = False" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "39306a1d", + "metadata": {}, + "outputs": [], + "source": [ + "emission_path = Path(os.getcwd()).parent.parent / 'my_emission_server' / 'e-mission-server'\n", + "sys.path.append(str(emission_path))\n", + "\n", + "# Also add the home (viz_scripts) to the path\n", + "sys.path.append('../viz_scripts')" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "94f673d6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "storage not configured, falling back to sample, default configuration\n", + "URL not formatted, defaulting to Stage_database\n", + "Connecting to database URL localhost\n" + ] + } + ], + "source": [ + "import scaffolding\n", + "import emission.core.get_database as edb\n", + "import emission.storage.timeseries.abstract_timeseries as esta" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "e171e277", + "metadata": {}, + "outputs": [], + "source": [ + "DB_SOURCE = [\n", + " \"Stage_database\", # Does NOT have composite trips BUT has section modes and distances\n", + " \"openpath_prod_durham\", # Has composite trips\n", + " \"openpath_prod_mm_masscec\", # Has composite trips\n", + " \"openpath_prod_ride2own\", # Has composite trips\n", + "# \"openpath_prod_uprm_civic\", # No replaced mode (Excluded)\n", + " \"openpath_prod_uprm_nicr\" # Has composite trips\n", + "]" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "70fa3112", + "metadata": {}, + "outputs": [], + "source": [ + "CURRENT_DB = DB_SOURCE[0]\n", + "\n", + "assert CURRENT_DB in DB_SOURCE" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "bbde79d1", + "metadata": {}, + "outputs": [], + "source": [ + "# ['p_micro', 'no_trip', 's_car', 'transit', 'car', 's_micro', 'ridehail', 'walk', 'unknown']\n", + "\n", + "REPLACED_MODE_DICT = {\n", + " \"Stage_database\": {\n", + " 'no_trip': 'no_trip',\n", + " 'no_travel': 'no_trip',\n", + " 'Unknown': 'unknown',\n", + " 'unknown': 'unknown',\n", + " 'bus': 'transit',\n", + " 'drove_alone': 'car',\n", + " 'bike': 'p_micro',\n", + " 'shared_ride': 's_car',\n", + " 'walk': 'walk',\n", + " 'train': 'transit',\n", + " 'bikeshare': 's_micro',\n", + " 'not_a trip': 'no_trip',\n", + " 'pilot_ebike': 'p_micro',\n", + " 'electric_car': 'car',\n", + " 'taxi': 'ridehail',\n", + " 'not_a_trip': 'no_trip',\n", + " 'run': 'walk',\n", + " 'scootershare': 's_micro',\n", + " 'tramway': 'transit',\n", + " 'free_shuttle': 'transit',\n", + " 'e-bike': 'p_micro',\n", + " 'rental_car': 'car',\n", + " 'train_+ bus': 'transit',\n", + " 'skateboard': 'p_micro',\n", + " 'snowboarding': 'p_micro',\n", + " 'e_bike': 'p_micro',\n", + " 'golf_cart': 'unknown',\n", + " 'emergency_vehicle with others': 's_car',\n", + " 'call_friend': 's_car',\n", + " 'no_replacement': 'no_travel',\n", + " 'doing_nothing': 'no_trip',\n", + " 'na': 'no_trip',\n", + " 'ebike': 'p_micro',\n", + " 'hiking': 'walk',\n", + " 'n/a': 'no_trip',\n", + " 'testing': 'unknown',\n", + " 'home': 'no_trip',\n", + " 'must_walk 3-5 mi a day for back': 'walk',\n", + " 'family': 's_car',\n", + " 'car': 'car',\n", + " 'pilot_e-bike': 'p_micro',\n", + " 'pilot_bike': 'p_micro',\n", + " 'time_spent on the clock at amazon': 'no_trip',\n", + " 'working': 'no_trip',\n", + " 'walk_at work': 'walk',\n", + " 'sitting_on my butt doing nothing': 'no_trip',\n", + " 'nothing._delivered food for work': 'no_trip',\n", + " 'train,_bus and walk': 'transit',\n", + " 'work_vehicle': 'car',\n", + " 'friend_picked me up': 's_car',\n", + " 'ski': 'p_micro',\n", + " 'not_accurate': 'unknown',\n", + " 'stolen_ebike': 'p_micro'\n", + " },\n", + " \"openpath_prod_durham\": {\n", + " 'Unknown': 'unknown',\n", + " 'bike': 'p_micro',\n", + " 'shared_ride': 's_car',\n", + " 'drove_alone': 'car',\n", + " 'bus': 'transit',\n", + " 'no_travel': 'no_trip',\n", + " 'scootershare': 's_micro',\n", + " 'walk': 'walk',\n", + " 'taxi': 'ridehail',\n", + " 'e_car_drove_alone': 'car',\n", + " 'bikeshare': 's_micro',\n", + " 'ebike': 'p_micro',\n", + " 'train': 'transit',\n", + " 'e_car_shared_ride': 's_car'\n", + " },\n", + " \"openpath_prod_mm_masscec\": {\n", + " 'Unknown': 'unknown',\n", + " 'drove_alone': 'car',\n", + " 'walk': 'walk',\n", + " 'shared_ride': 's_car',\n", + " 'bike': 'p_micro',\n", + " 'bikeshare': 's_micro',\n", + " 'no_travel': 'no_trip',\n", + " 'taxi': 'ridehail',\n", + " 'bus': 'transit',\n", + " 'scootershare': 's_micro',\n", + " 'train': 'transit',\n", + " 'walking': 'walk',\n", + " 'e_car_drove_alone': 'car'\n", + " },\n", + " \"openpath_prod_ride2own\": {\n", + " 'Unknown': 'unknown',\n", + " 'drove_alone': 'car',\n", + " 'walk': 'walk',\n", + " 'shared_ride': 's_car',\n", + " 'bike': 'p_micro',\n", + " 'no_travel': 'no_trip',\n", + " 'taxi': 'ridehail',\n", + " 'bus': 'transit',\n", + " 'train': 'transit',\n", + " 'e_car_drove_alone': 'car',\n", + " 'e_car_shared_ride': 's_car'\n", + " },\n", + " \"openpath_prod_uprm_nicr\": {\n", + " 'Unknown': 'unknown',\n", + " 'walk': 'walk',\n", + " 'drove_alone': 'car'\n", + " }\n", + "}\n", + "\n", + "SENSED_SECTION_DICT = {\n", + " \"openpath_prod_mm_masscec\": {'AIR_OR_HSR', 'BICYCLING', 'BUS', 'CAR', 'LIGHT_RAIL', 'SUBWAY', 'TRAIN', 'UNKNOWN', 'WALKING'}\n", + "}\n", + "\n", + "SURVEY_DATA_DICT = {\n", + " \"Stage_database\": {\n", + " \"Unique User ID (auto-filled, do not edit)\": \"user_id\",\n", + " \"In which year were you born?\": \"birth_year\",\n", + " \"What is your gender?\": \"gender\",\n", + " \"Do you have a valid driver's license?\": \"has_drivers_license\",\n", + " \"Are you a student?\": \"is_student\",\n", + " \"What is the highest grade or degree that you have completed?\": \"highest_education\",\n", + " \"Do you work for either pay or profit?\": \"is_paid\",\n", + " \"Do you have more than one job?\": \"has_multiple_jobs\",\n", + " \"Do you work full-time or part-time at your primary job?\": \"primary_job_type\",\n", + " \"Which best describes your primary job?\": \"primary_job_description\",\n", + " \"How did you usually get to your primary job last week? \": \"primary_job_commute_mode\",\n", + " \"Thinking about your daily commute to work last week, how many minutes did it usually take to get from home to the primary job/work place?\": \"primary_job_commute_time\",\n", + " \"At your primary job, do you have the ability to set or change your own start time?\": \"is_primary_job_flexible\",\n", + " \"Do you have the option of working from home or an alternate location instead of going into your primary work place?\": \"primary_job_can_wfh\",\n", + " \"How many days per week do you usually work from home or an alternate location?\": \"wfh_days\",\n", + " \"Do you own or rent your place of residence?\": \"residence_ownership_type\",\n", + " \"What is your home type?\": \"residence_type\",\n", + " \"Please identify which category represents your total household income, before taxes, for last year.\": \"income_category\",\n", + " \"Including yourself, how many people live in your home?\": \"n_residence_members\",\n", + " \"How many children under age 18 live in your home?\": \"n_residents_u18\",\n", + " \"Including yourself, how many people have a driver's license in your household?\": \"n_residents_with_license\",\n", + " \"How many motor vehicles are owned, leased, or available for regular use by the people who currently live in your household?\": \"n_motor_vehicles\",\n", + " \"If you were unable to use your household vehicle(s), which of the following options would be available to you to get you from place to place?\": \"available_modes\",\n", + " \"Do you have a medical condition that makes it difficult to travel outside of the home?\": \"has_medical_condition\",\n", + " \"How long have you had this condition?\": \"medical_condition_duration\"\n", + " },\n", + " # Retrieved from: e-mission-phone/survey-resources/data-xls/demo-survey-v1.xlsx\n", + " \"openpath_prod_durham\": {\n", + " \"At_your_primary_job_do_you_ha\": \"is_primary_job_flexible\",\n", + " \"Which_best_describes_your_prim\": \"primary_job_description\",\n", + " \"Do_you_work_full_time_or_part_\": \"primary_job_type\",\n", + " \"Do_you_have_the_option_of_work\": \"primary_job_can_wfh\",\n", + " \"Please_describe_your_primary_job\": \"primary_job_description_2\",\n", + " \"Do_you_have_more_than_one_job\": \"has_multiple_jobs\",\n", + " # Two columns: how many days/week do you work & what days of the week do you work. \n", + " # the latter has only 4 NA values, the former has 45 NA values.\n", + " \"What_days_of_the_week_do_you_t\": \"wfh_days\",\n", + " \"How_many_days_do_you_usually_w_001\": \"n_wfh_days\",\n", + " # All these are NAs.\n", + " \"Which_one_below_describe_you_b\": \"description\",\n", + " \"What_is_your_race_ethnicity\": \"race_or_ethnicity\",\n", + " \"Are_you_a_student\": \"is_student\",\n", + " \"What_is_the_highest_grade_or_d\": \"highest_education\",\n", + " \"do_you_consider_yourself_to_be\": \"is_transgender\",\n", + " \"What_is_your_gender\": \"gender\",\n", + " \"How_old_are_you\": \"age\",\n", + " \"Are_you_a_paid_worker\": \"is_paid\",\n", + " \"Do_you_have_a_driver_license\": \"has_drivers_license\",\n", + " \"How_long_you_had_this_conditio\": \"medical_condition_duration\",\n", + " \"Including_yourself_how_many_w_001\": \"n_residents_u18\",\n", + " \"Including_yourself_how_many_p\": \"n_residence_members\",\n", + " \"Do_you_own_or_rent_your_home\": \"residence_ownership_type\",\n", + " \"Please_identify_which_category\": \"income_category\",\n", + " \"If_you_were_unable_to_use_your\": \"available_modes\",\n", + " \"Including_yourself_how_many_p_001\": \"n_residents_with_license\",\n", + " \"Including_yourself_how_many_w\": \"n_working_residents\",\n", + " \"What_is_your_home_type\": \"residence_type\",\n", + " \"How_many_motor_vehicles_are_ow\": \"n_motor_vehicles\",\n", + " \"Do_you_have_a_condition_or_han\": \"has_medical_condition\"\n", + " },\n", + " \"openpath_prod_mm_masscec\": {\n", + " # Same questions as Durham.\n", + " \"At_your_primary_job_do_you_ha\": \"is_primary_job_flexible\",\n", + " \"Which_best_describes_your_prim\": \"primary_job_description\",\n", + " \"Do_you_work_full_time_or_part_\": \"primary_job_type\",\n", + " \"Do_you_have_the_option_of_work\": \"primary_job_can_wfh\",\n", + " \"Please_describe_your_primary_job\": \"primary_job_description_2\",\n", + " \"Do_you_have_more_than_one_job\": \"has_multiple_jobs\",\n", + " # Two columns: how many days/week do you work & what days of the week do you work. \n", + " # the latter has only 4 NA values, the former has 45 NA values.\n", + " \"What_days_of_the_week_do_you_t\": \"wfh_days\",\n", + " \"How_many_days_do_you_usually_w_001\": \"n_wfh_days\",\n", + " # All these are NAs.\n", + " \"Which_one_below_describe_you_b\": \"description\",\n", + " \"What_is_your_race_ethnicity\": \"race_or_ethnicity\",\n", + " \"Are_you_a_student\": \"is_student\",\n", + " \"What_is_the_highest_grade_or_d\": \"highest_education\",\n", + " \"do_you_consider_yourself_to_be\": \"is_transgender\",\n", + " \"What_is_your_gender\": \"gender\",\n", + " \"How_old_are_you\": \"age\",\n", + " \"Are_you_a_paid_worker\": \"is_paid\",\n", + " \"Do_you_have_a_driver_license\": \"has_drivers_license\",\n", + " \"How_long_you_had_this_conditio\": \"medical_condition_duration\",\n", + " \"Including_yourself_how_many_w_001\": \"n_residents_u18\",\n", + " \"Including_yourself_how_many_p\": \"n_residence_members\",\n", + " \"Do_you_own_or_rent_your_home\": \"residence_ownership_type\",\n", + " \"Please_identify_which_category\": \"income_category\",\n", + " \"If_you_were_unable_to_use_your\": \"available_modes\",\n", + " \"Including_yourself_how_many_p_001\": \"n_residents_with_license\",\n", + " \"Including_yourself_how_many_w\": \"n_working_residents\",\n", + " \"What_is_your_home_type\": \"residence_type\",\n", + " \"How_many_motor_vehicles_are_ow\": \"n_motor_vehicles\",\n", + " \"Do_you_have_a_condition_or_han\": \"has_medical_condition\"\n", + " },\n", + " \"openpath_prod_ride2own\": {\n", + " # Same questions as Durham.\n", + " \"How_old_are_you\": \"age\",\n", + " \"What_is_your_gender\": \"gender\",\n", + " \"do_you_consider_yourself_to_be\": \"is_transgender\",\n", + " \"What_is_your_race_ethnicity\": \"race_or_ethnicity\",\n", + " \"Do_you_have_a_driver_license\": \"has_drivers_license\",\n", + " \"Are_you_a_student\": \"is_student\",\n", + " \"What_is_the_highest_grade_or_d\": \"highest_education\",\n", + " \"Are_you_a_paid_worker\": \"is_paid\",\n", + " \"Which_one_below_describe_you_b\": \"description\",\n", + " \"Do_you_own_or_rent_your_home\": \"residence_ownership_type\",\n", + " \"What_is_your_home_type\": \"residence_type\",\n", + " \"Please_identify_which_category\": \"income_category\",\n", + " \"Including_yourself_how_many_p\": \"n_residence_members\",\n", + " \"Including_yourself_how_many_w\": \"n_working_residents\",\n", + " \"Including_yourself_how_many_p_001\": \"n_residents_with_license\",\n", + " \"Including_yourself_how_many_w_001\": \"n_residents_u18\",\n", + " \"How_many_motor_vehicles_are_ow\": \"n_motor_vehicles\",\n", + " \"If_you_were_unable_to_use_your\": \"available_modes\",\n", + " \"Do_you_have_a_condition_or_han\": \"has_medical_condition\",\n", + " \"How_long_you_had_this_conditio\": \"medical_condition_duration\",\n", + " \"Do_you_have_more_than_one_job\": \"has_multiple_jobs\",\n", + " \"Do_you_work_full_time_or_part_\": \"primary_job_type\",\n", + " \"Which_best_describes_your_prim\": \"primary_job_description\",\n", + " \"Please_describe_your_primary_job\": \"primary_job_description_2\",\n", + " \"At_your_primary_job_do_you_ha\": \"is_primary_job_flexible\",\n", + " \"Do_you_have_the_option_of_work\": \"primary_job_can_wfh\",\n", + " \"How_many_days_do_you_usually_w_001\": \"n_wfh_days\",\n", + " \"What_days_of_the_week_do_you_t\": \"wfh_days\"\n", + " },\n", + " \"openpath_prod_uprm_nicr\": {\n", + " # Same as Durham!\n", + " \"At_your_primary_job_do_you_ha\": \"is_primary_job_flexible\",\n", + " \"Which_best_describes_your_prim\": \"primary_job_description\",\n", + " \"Do_you_work_full_time_or_part_\": \"primary_job_type\",\n", + " \"Do_you_have_the_option_of_work\": \"primary_job_can_wfh\",\n", + " \"Please_describe_your_primary_job\": \"primary_job_description_2\",\n", + " \"Do_you_have_more_than_one_job\": \"has_multiple_jobs\",\n", + " # Two columns: how many days/week do you work & what days of the week do you work. \n", + " # the latter has only 4 NA values, the former has 45 NA values.\n", + " \"What_days_of_the_week_do_you_t\": \"wfh_days\",\n", + " \"How_many_days_do_you_usually_w_001\": \"n_wfh_days\",\n", + " # All these are NAs.\n", + " \"Which_one_below_describe_you_b\": \"description\",\n", + " \"What_is_your_race_ethnicity\": \"race_or_ethnicity\",\n", + " \"Are_you_a_student\": \"is_student\",\n", + " \"What_is_the_highest_grade_or_d\": \"highest_education\",\n", + " \"do_you_consider_yourself_to_be\": \"is_transgender\",\n", + " \"What_is_your_gender\": \"gender\",\n", + " \"How_old_are_you\": \"age\",\n", + " \"Are_you_a_paid_worker\": \"is_paid\",\n", + " \"Do_you_have_a_driver_license\": \"has_drivers_license\",\n", + " \"How_long_you_had_this_conditio\": \"medical_condition_duration\",\n", + " \"Including_yourself_how_many_w_001\": \"n_residents_u18\",\n", + " \"Including_yourself_how_many_p\": \"n_residence_members\",\n", + " \"Do_you_own_or_rent_your_home\": \"residence_ownership_type\",\n", + " \"Please_identify_which_category\": \"income_category\",\n", + " \"If_you_were_unable_to_use_your\": \"available_modes\",\n", + " \"Including_yourself_how_many_p_001\": \"n_residents_with_license\",\n", + " \"Including_yourself_how_many_w\": \"n_working_residents\",\n", + " \"What_is_your_home_type\": \"residence_type\",\n", + " \"How_many_motor_vehicles_are_ow\": \"n_motor_vehicles\",\n", + " \"Do_you_have_a_condition_or_han\": \"has_medical_condition\"\n", + " }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "69008893", + "metadata": {}, + "outputs": [], + "source": [ + "## Source: db_utils.py in op-admin-dashboard.\n", + "\n", + "BINARY_DEMOGRAPHICS_COLS = [\n", + " 'user_id',\n", + " '_id',\n", + "]\n", + "\n", + "EXCLUDED_DEMOGRAPHICS_COLS = [\n", + " 'data.xmlResponse', \n", + " 'data.name',\n", + " 'data.version',\n", + " 'data.label',\n", + " 'xmlns:jr',\n", + " 'xmlns:orx',\n", + " 'id',\n", + " 'start',\n", + " 'end',\n", + " 'attrxmlns:jr',\n", + " 'attrxmlns:orx',\n", + " 'attrid',\n", + " '__version__',\n", + " 'attrversion',\n", + " 'instanceID',\n", + "]" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "12cc0c54", + "metadata": {}, + "outputs": [], + "source": [ + "## Source: scaffolding.py\n", + "\n", + "def expand_userinputs(labeled_ct):\n", + " '''\n", + " param: labeled_ct: a dataframe of confirmed trips, some of which have labels\n", + " params: labels_per_trip: the number of labels for each trip.\n", + " Currently, this is 2 for studies and 3 for programs, and should be \n", + " passed in by the notebook based on the input config.\n", + " If used with a trip-level survey, it could be even larger.\n", + " '''\n", + " # CASE 1 of https://github.com/e-mission/em-public-dashboard/issues/69#issuecomment-1256835867\n", + " if len(labeled_ct) == 0:\n", + " return labeled_ct\n", + " label_only = pd.DataFrame(labeled_ct.user_input.to_list(), index=labeled_ct.index)\n", + " # disp.display(label_only.head())\n", + " labels_per_trip = len(label_only.columns)\n", + " print(\"Found %s columns of length %d\" % (label_only.columns, labels_per_trip))\n", + " expanded_ct = pd.concat([labeled_ct, label_only], axis=1)\n", + " assert len(expanded_ct) == len(labeled_ct), \\\n", + " (\"Mismatch after expanding labels, expanded_ct.rows = %s != labeled_ct.rows %s\" %\n", + " (len(expanded_ct), len(labeled_ct)))\n", + " print(\"After expanding, columns went from %s -> %s\" %\n", + " (len(labeled_ct.columns), len(expanded_ct.columns)))\n", + " assert len(expanded_ct.columns) == len(labeled_ct.columns) + labels_per_trip, \\\n", + " (\"Mismatch after expanding labels, expanded_ct.columns = %s != labeled_ct.columns %s\" %\n", + " (len(expanded_ct.columns), len(labeled_ct.columns)))\n", + " # disp.display(expanded_ct.head())\n", + " return expanded_ct" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "9a98e2fb", + "metadata": {}, + "outputs": [], + "source": [ + "## Source: scaffolding.py\n", + "\n", + "def data_quality_check(expanded_ct):\n", + " '''1. Delete rows where the mode_confirm was pilot_ebike and repalced_mode was pilot_ebike.\n", + " 2. Delete rows where the mode_confirm was pilot_ebike and repalced_mode was same_mode.\n", + " 3. Replace same_mode for the mode_confirm for Energy Impact Calcualtion.'''\n", + "\n", + " # TODO: This is only really required for the initial data collection around the minipilot\n", + " # in subsequent deployes, we removed \"same mode\" and \"pilot_ebike\" from the options, so the\n", + " # dataset did not contain of these data quality issues\n", + "\n", + " if 'replaced_mode' in expanded_ct.columns:\n", + " expanded_ct.drop(expanded_ct[(expanded_ct['mode_confirm'] == 'pilot_ebike') & (expanded_ct['replaced_mode'] == 'pilot_ebike')].index, inplace=True)\n", + " expanded_ct.drop(expanded_ct[(expanded_ct['mode_confirm'] == 'pilot_ebike') & (expanded_ct['replaced_mode'] == 'same_mode')].index, inplace=True)\n", + " expanded_ct['replaced_mode'] = np.where(expanded_ct['replaced_mode'] == 'same_mode',expanded_ct['mode_confirm'], expanded_ct['replaced_mode'])\n", + " \n", + " return expanded_ct" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "fe37bf27", + "metadata": {}, + "outputs": [], + "source": [ + "if CURRENT_DB != \"Stage_database\":\n", + "\n", + " ## Source: scaffolding.py\n", + "\n", + " uuid_df = pd.json_normalize(list(edb.get_uuid_db().find()))\n", + "\n", + " if not INCLUDE_TEST_USERS:\n", + " uuid_df = uuid_df.loc[~uuid_df.user_email.str.contains('_test_'), :]\n", + "\n", + " filtered = uuid_df.uuid.unique()\n", + "\n", + " agg = esta.TimeSeries.get_aggregate_time_series()\n", + " all_ct = agg.get_data_df(\"analysis/confirmed_trip\", None)\n", + "\n", + " print(f\"Before filtering, length={len(all_ct)}\")\n", + " participant_ct_df = all_ct.loc[all_ct.user_id.isin(filtered), :]\n", + " print(f\"After filtering, length={len(participant_ct_df)}\")\n", + "\n", + " expanded_ct = expand_userinputs(participant_ct_df)\n", + " expanded_ct = data_quality_check(expanded_ct)\n", + " print(expanded_ct.columns.tolist())\n", + " expanded_ct['replaced_mode'] = expanded_ct['replaced_mode'].fillna('Unknown')" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "13536d14", + "metadata": {}, + "outputs": [], + "source": [ + "# # Additional preprocessing for replaced mode (if any)\n", + "\n", + "if CURRENT_DB != \"Stage_database\":\n", + "\n", + " mode_counts = expanded_ct['replaced_mode'].value_counts()\n", + " drop_modes = mode_counts[mode_counts == 1].index.tolist()\n", + "\n", + " expanded_ct.drop(\n", + " index=expanded_ct.loc[expanded_ct.replaced_mode.isin(drop_modes)].index,\n", + " inplace=True\n", + " )\n", + "\n", + " # Additional modes to drop.\n", + " expanded_ct.drop(\n", + " index=expanded_ct.loc[expanded_ct.replaced_mode.isin(\n", + " # Remove all rows with air, boat, or weird answers.\n", + " ['houseboat', 'gondola', 'airline_flight', 'aircraft', 'zoo', 'air',\n", + " 'airplane', 'boat', 'flight', 'plane', 'meal', 'lunch']\n", + " )].index,\n", + " inplace=True\n", + " )\n", + " \n", + " expanded_ct.replaced_mode = expanded_ct.replaced_mode.apply(lambda x: REPLACED_MODE_DICT[CURRENT_DB][x])" + ] + }, + { + "cell_type": "markdown", + "id": "258844f4", + "metadata": {}, + "source": [ + "# Demographic pre-processing" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "7461a4d2", + "metadata": {}, + "outputs": [], + "source": [ + "# Demographics\n", + "\n", + "if CURRENT_DB != \"Stage_database\":\n", + "\n", + " decoded_uuids = [str(x) for x in filtered]\n", + "\n", + " ## Source: query_demographics() in op-admin-dashboard.\n", + " ts = esta.TimeSeries.get_aggregate_time_series()\n", + " entries = list(ts.find_entries([\"manual/demographic_survey\"]))\n", + "\n", + " available_key = {}\n", + " for entry in entries:\n", + " survey_key = list(entry['data']['jsonDocResponse'].keys())[0]\n", + " if survey_key not in available_key:\n", + " available_key[survey_key] = []\n", + "\n", + " # Minor modification: Added user_id check to filter users.\n", + " if str(entry['user_id']) in decoded_uuids:\n", + " available_key[survey_key].append(entry)\n", + "\n", + " dataframes = {}\n", + " for key, json_object in available_key.items():\n", + " df = pd.json_normalize(json_object)\n", + " dataframes[key] = df\n", + "\n", + " for key, df in dataframes.items():\n", + " if not df.empty:\n", + " for col in BINARY_DEMOGRAPHICS_COLS:\n", + " if col in df.columns:\n", + " df[col] = df[col].apply(str) \n", + " columns_to_drop = [col for col in df.columns if col.startswith(\"metadata\")]\n", + " df.drop(columns= columns_to_drop, inplace=True) \n", + " df.columns=[col.rsplit('.',1)[-1] if col.startswith('data.jsonDocResponse.') else col for col in df.columns]\n", + " for col in EXCLUDED_DEMOGRAPHICS_COLS:\n", + " if col in df.columns:\n", + " df.drop(columns= [col], inplace=True)\n", + "\n", + " survey_data = pd.DataFrame() \n", + " for v in dataframes.values():\n", + " survey_data = pd.concat([survey_data, v], axis=0, ignore_index=True)\n", + "else:\n", + " # Read the demographics.\n", + " survey_data = pd.read_csv('../viz_scripts/Can Do Colorado eBike Program - en.csv')\n", + " survey_data.rename(columns={'Unique User ID (auto-filled, do not edit)': 'user_id'}, inplace=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "fe5a9dff", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/var/folders/4x/l9lw50rn7qvf79m01f21x70mlpd6gh/T/ipykernel_79014/2914323964.py:6: DtypeWarning: Columns (40,41,42,47) have mixed types. Specify dtype option on import or set low_memory=False.\n", + " expanded_ct = pd.read_csv('../data/cached_allceo_data.csv')\n" + ] + } + ], + "source": [ + "if CURRENT_DB == \"Stage_database\":\n", + " \n", + " if os.path.exists('../data/cached_allceo_data.csv'):\n", + " \n", + " # Replace current instance of dataframe with the cached dataframe.\n", + " expanded_ct = pd.read_csv('../data/cached_allceo_data.csv')\n", + " expanded_ct.loc[expanded_ct.replaced_mode == 'no_travel', 'replaced_mode'] = 'no_trip'\n", + " else:\n", + " ## NOTE: Run this cell only if the cached CSV is not already available. It will take a LOT of time.\n", + " ## Benchmark timing: ~12 hours on a MacBook Pro (2017 model) with pandarallel, 4 workers.\n", + " \n", + " importlib.reload(scaffolding)\n", + " expanded_ct = scaffolding.get_section_durations(expanded_ct)\n", + " expanded_ct.to_csv('../data/cached_allceo_data.csv', index=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "a6be751e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "203 184\n" + ] + } + ], + "source": [ + "print(len(survey_data.user_id.unique()), len(expanded_ct.user_id.unique()))" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "9ebc87d8", + "metadata": {}, + "outputs": [], + "source": [ + "survey_data.rename(SURVEY_DATA_DICT[CURRENT_DB], axis='columns', inplace=True)" + ] + }, + { + "cell_type": "markdown", + "id": "522b1362", + "metadata": {}, + "source": [ + "### Demographic data preprocessing" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "336508c2", + "metadata": {}, + "outputs": [], + "source": [ + "print(survey_data.columns.tolist())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "29bc7996", + "metadata": {}, + "outputs": [], + "source": [ + "# gtg\n", + "survey_data['ft_job'] = survey_data.primary_job_type.apply(\n", + " lambda x: 1 if str(x).lower() == 'full_time' else 0\n", + ")\n", + "\n", + "# gtg\n", + "survey_data['multiple_jobs'] = survey_data.has_multiple_jobs.apply(\n", + " lambda x: 1 if str(x).lower() == 'yes' else 0\n", + ")\n", + "\n", + "# gtg\n", + "survey_data.loc[\n", + " survey_data.n_motor_vehicles.isin(\n", + " ['prefer_not_to_say', 'Prefer not to say / Prefiero no decir.']\n", + " ), 'n_motor_vehicles'\n", + "] = 0\n", + "survey_data.loc[survey_data.n_motor_vehicles.isin(['more_than_3', '4+', 'more_than_4']), 'n_motor_vehicles'] = 4\n", + "survey_data.n_motor_vehicles = survey_data.n_motor_vehicles.astype(int)\n", + "\n", + "# gtg\n", + "survey_data.has_drivers_license = survey_data.has_drivers_license.apply(\n", + " lambda x: 1 if str(x).lower() == 'yes' else 0\n", + ")\n", + "\n", + "survey_data.loc[survey_data.n_residents_u18 == 'prefer_not_to_say'] = 0\n", + "survey_data.n_residents_u18 = survey_data.n_residents_u18.astype(int)\n", + "\n", + "survey_data.loc[survey_data.n_residence_members == 'prefer_not_to_say'] = 0\n", + "survey_data.n_residence_members = survey_data.n_residence_members.astype(int)\n", + "\n", + "survey_data.loc[survey_data.n_residents_with_license == 'prefer_not_to_say'] = 0\n", + "survey_data.loc[survey_data.n_residents_with_license == 'more_than_4'] = 4\n", + "survey_data.n_residents_with_license = survey_data.n_residents_with_license.astype(int)\n", + "\n", + "# In allCEO, we see 50 & 9999. What??\n", + "survey_data = survey_data[\n", + " (survey_data.n_residence_members < 10) & (survey_data.n_residents_u18 < 10) & \n", + " (survey_data.n_residents_with_license < 10) & \n", + " (survey_data.n_residence_members - survey_data.n_residents_with_license > 0) &\n", + " (survey_data.n_residence_members - survey_data.n_residents_u18 > 0)\n", + "].reset_index(drop=True)\n", + "\n", + "# gtg\n", + "if CURRENT_DB != \"Stage_database\":\n", + " survey_data.n_working_residents = survey_data.n_working_residents.apply(\n", + " lambda x: 0 if x == 'prefer_not_to_say' else int(x)\n", + " )\n", + "else:\n", + " survey_data['n_working_residents'] = survey_data['n_residence_members'] - survey_data['n_residents_u18']\n", + " \n", + "survey_data = survey_data[survey_data.n_working_residents >= 0].reset_index(drop=True)\n", + "\n", + "# gtg\n", + "survey_data.is_paid = survey_data.is_paid.apply(lambda x: 1 if x == 'Yes' else 0)\n", + "\n", + "# gtg\n", + "survey_data.has_medical_condition = survey_data.has_medical_condition.apply(\n", + " lambda x: 1 if str(x).lower() == 'yes' else 0\n", + ")\n", + "\n", + "## gtg\n", + "survey_data.is_student.replace({\n", + " 'Not a student': 0, \n", + " 'Yes - Full Time College/University': 1,\n", + " 'Yes - Vocation/Technical/Trade School': 1,\n", + " 'Yes - K-12th Grade including GED': 1, \n", + " 'Work': 0, \n", + " 'No': 0,\n", + " 'Prefer not to say': 0,\n", + " 'Yes - Part-Time College/University': 1,\n", + " 'Taking prerequisites missing for grad program ': 1, \n", + " 'Graduate': 1,\n", + " 'Custodian': 0, \n", + " 'Work at csu': 0,\n", + " 'not_a_student': 0, \n", + " 'yes___vocation_technical_trade_school': 1,\n", + " 'yes___part_time_college_university': 1,\n", + " 'prefer_not_to_say': 0, \n", + " 'yes___k_12th_grade_including_ged': 1,\n", + " 'yes___full_time_college_university': 1\n", + "}, inplace=True)" + ] + }, + { + "cell_type": "markdown", + "id": "aeb85637", + "metadata": {}, + "source": [ + "### Additinal Demographic Data Preprocessing" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9c069bd2", + "metadata": {}, + "outputs": [], + "source": [ + "if CURRENT_DB == \"Stage_database\":\n", + " age = survey_data.birth_year.apply(\n", + " lambda x: 2024 - int(x) if int(x) > 100 else int(x)\n", + " )\n", + " \n", + " upper = age - (age % 5)\n", + " lower = upper + 5\n", + " new_col = (upper + 1).astype(str) + '___' + lower.astype(str) + '_years_old'\n", + " survey_data['age'] = new_col\n", + " \n", + " survey_data.loc[survey_data.age.isin([\n", + " '66___70_years_old', '76___80_years_old', '81___85_years_old'\n", + " ]), 'age'] = '__65_years_old'\n", + " \n", + " survey_data.drop(columns=['birth_year'], inplace=True)\n", + "\n", + "else:\n", + " survey_data = survey_data[survey_data.age != 0].reset_index(drop=True)\n", + "\n", + "if survey_data.columns.isin(['primary_job_commute_mode', 'primary_job_commute_time']).all():\n", + " survey_data.drop(columns=['primary_job_commute_mode', 'primary_job_commute_time'], inplace=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f094cadd", + "metadata": {}, + "outputs": [], + "source": [ + "def normalize_job_descriptions(db_name, df):\n", + " if db_name != 'Stage_database':\n", + " PRIMARY_JOB_DESCRIPTION_DICT = {\n", + " \"sales_or_service\": \"Sales or service\",\n", + " \"other\": \"Other\",\n", + " \"\": \"Other\",\n", + " \"professional__managerial__or_technical\": \"Professional, Manegerial, or Technical\",\n", + " \"manufacturing__construction__maintenance\": \"Manufacturing, construction, maintenance, or farming\",\n", + " \"clerical_or_administrative_support\": \"Clerical or administrative support\",\n", + " \"prefer_not_to_say\": \"Prefer not to say\",\n", + " }\n", + " \n", + " df.primary_job_description = df.primary_job_description.apply(\n", + " lambda x: PRIMARY_JOB_DESCRIPTION_DICT[x]\n", + " )\n", + " else:\n", + " df.primary_job_description = df.primary_job_description.str.strip()\n", + "\n", + " # Normalize the job description. Inspired from the 'e-bike trips by occupation' \n", + " # plot in the CanBikeCo full pilot paper.\n", + " df.loc[\n", + " df.primary_job_description.isin([\n", + " 'Paraprofessional', 'Education', 'education/early childhood', 'Teacher',\n", + " 'Education non-profit manager', 'Scientific research', 'Research',\n", + " 'Preschool Tracher'\n", + " ]), 'primary_job_description'\n", + " ] = 'Education'\n", + "\n", + " df.loc[\n", + " df.primary_job_description.isin([\n", + " 'Custodian', 'Custodial', 'Csu custodian', 'Janitorial',\n", + " 'Custodial Maintanace'\n", + " ]), 'primary_job_description'\n", + " ] = 'Custodial'\n", + "\n", + " df.loc[\n", + " df.primary_job_description.isin([\n", + " 'Inbound cs', 'Accounting Technician', \n", + " 'Clerical'\n", + " ]), 'primary_job_description'\n", + " ] = 'Clerical or administrative support'\n", + "\n", + " df.loc[\n", + " df.primary_job_description.isin([\n", + " 'Restaurant manager', 'Transportaion Services',\n", + " ]), 'primary_job_description'\n", + " ] = 'Sales or service'\n", + "\n", + " df.loc[\n", + " df.primary_job_description.isin([\n", + " 'Pastry chef and line cook', 'Cook', 'Chef', 'Dining Services',\n", + " 'Food Service', 'Cooking', 'Residential Dining Services', 'Line Cook'\n", + " ]), 'primary_job_description'\n", + " ] = 'Food service'\n", + "\n", + " df.loc[\n", + " df.primary_job_description.isin([\n", + " 'CNA', 'Caregiver/ Qmap', 'Health care', 'Nurse',\n", + " 'Healthcare', 'Medical', 'Medical field',\n", + " 'Family support'\n", + " ]), 'primary_job_description'\n", + " ] = 'Medical/healthcare'\n", + "\n", + " df.loc[\n", + " df.primary_job_description.isin([\n", + " 'Amazon', 'Hockey rink', 'Caregiver', 'Security', 'Nonprofit social work',\n", + " 'Therapeutic', 'Driver'\n", + " ]), 'primary_job_description'\n", + " ] = 'Other'\n", + "\n", + " df.loc[\n", + " df.primary_job_description.isin([\n", + " 'Hospital laundry', 'Matreal handler', 'Maintenance',\n", + " 'Co op laundry'\n", + " ]), 'primary_job_description'\n", + " ] = 'Manufacturing, construction, maintenance, or farming'\n", + "\n", + " df.loc[df.primary_job_description.isna(), 'primary_job_description'] = 'Other'\n", + "\n", + " return df" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0bf37859", + "metadata": {}, + "outputs": [], + "source": [ + "INCOME_DICT = {\n", + " 'Stage_database': {\n", + " 'Prefer not to say': 0,\n", + " 'Less than $24,999': 1,\n", + " '$25,000-$49,999': 2,\n", + " '$50,000-$99,999': 3,\n", + " '$100,000 -$149,999': 4,\n", + " '$150,000-$199,999': 5,\n", + " '$150,000': 5,\n", + " '$150,000-$199,999': 6,\n", + " '$200,000 or more': 7\n", + " },\n", + " 'Others': {\n", + " 'prefer_not_to_say': 0, \n", + " 'less_than__24_999': 1,\n", + " '_25_000_to__49_999': 2,\n", + " '_50_000_to__99_999': 3,\n", + " '_100_000_to__149_999': 4,\n", + " '_150_000_to__199_999': 5\n", + " }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "42b3163a", + "metadata": {}, + "outputs": [], + "source": [ + "survey_data = normalize_job_descriptions(CURRENT_DB, survey_data)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fe2b18b6", + "metadata": {}, + "outputs": [], + "source": [ + "if CURRENT_DB == 'Stage_database':\n", + " survey_data.income_category = survey_data.income_category.apply(\n", + " lambda x: INCOME_DICT['Stage_database'][x]\n", + " )\n", + "else:\n", + " survey_data.income_category = survey_data.income_category.apply(\n", + " lambda x: INCOME_DICT['Others'][x]\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b36672b9", + "metadata": {}, + "outputs": [], + "source": [ + "from sklearn.preprocessing import OneHotEncoder\n", + "\n", + "def generate_ohe_features(df, feature_name):\n", + " ohe = OneHotEncoder()\n", + " ohe.fit(df[[feature_name]])\n", + " return pd.DataFrame(\n", + " ohe.transform(df[[feature_name]]).todense(), \n", + " columns=ohe.get_feature_names_out(),\n", + " index=df.index\n", + " ), ohe" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dc8d1846", + "metadata": {}, + "outputs": [], + "source": [ + "survey_data.reset_index(drop=True, inplace=True)\n", + "\n", + "ohe_features = ['highest_education', 'primary_job_description', 'gender', 'age']\n", + "\n", + "for ohe in ohe_features:\n", + " df, _ = generate_ohe_features(survey_data, ohe)\n", + " survey_data = survey_data.merge(right=df, left_index=True, right_index=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d2d6f8c1", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "to_drop = [\n", + " 'Timestamp', 'gender', 'highest_education', 'primary_job_type', 'primary_job_description', \n", + " 'primary_job_commute_mode', 'primary_job_commute_time', 'is_primary_job_flexible', \n", + " 'primary_job_can_wfh', 'wfh_days', 'Which one below describe you best?', 'residence_ownership_type', \n", + " 'residence_type', 'medical_condition_duration', 'has_multiple_jobs', 'age', '_id', 'data.ts',\n", + " 'primary_job_description_2', 'wfh_days', 'n_wfh_days', 'description', 'race_or_ethnicity', \n", + " 'highest_education', 'is_transgender', 'medical_condition_duration'\n", + "]\n", + "\n", + "for column in to_drop:\n", + " if column in survey_data.columns:\n", + " survey_data.drop(columns=[column], inplace=True)" + ] + }, + { + "cell_type": "markdown", + "id": "65039f73", + "metadata": {}, + "source": [ + "## Merge sensed data and demographics" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c7eb2e09", + "metadata": {}, + "outputs": [], + "source": [ + "# Additional preprocessing to filter unwanted users from sensed trips data.\n", + "expanded_ct['user_id_join'] = expanded_ct['user_id'].apply(lambda x: str(x).replace('-', ''))\n", + "survey_data['user_id_join'] = survey_data['user_id'].apply(lambda x: str(x).replace('-', ''))\n", + "\n", + "survey_data.rename(columns={'user_id': 'survey_user_id'}, inplace=True)\n", + "\n", + "common = set(expanded_ct.user_id_join.unique()).intersection(\n", + " set(survey_data.user_id_join.unique())\n", + ")\n", + "\n", + "filtered_trips = expanded_ct.loc[expanded_ct.user_id_join.isin(common), :].reset_index(drop=True)\n", + "filtered_survey = survey_data.loc[survey_data.user_id_join.isin(common), :].reset_index(drop=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "53927d5f", + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "# Just to double-check.\n", + "print(len(filtered_trips.user_id.unique()), len(filtered_survey.survey_user_id.unique()))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "daed8fb0", + "metadata": {}, + "outputs": [], + "source": [ + "# Compute the section_*_argmax.\n", + "\n", + "def compute_argmax(db: str, row):\n", + " \n", + " if db != 'Stage_database':\n", + " \n", + " sections = row['inferred_section_summary']\n", + "\n", + " if pd.isna(sections) or len(sections) == 0 or len(sections['distance']) == 0:\n", + " return row\n", + "\n", + " try:\n", + " mode = sorted(sections['distance'].items(), key=lambda x: x[-1], reverse=True)[0][0]\n", + " distance = sections['distance'][mode]\n", + " duration = sections['duration'][mode]\n", + "\n", + " row['section_mode_argmax'] = mode\n", + " row['section_distance_argmax'] = distance\n", + " row['section_duration_argmax'] = duration\n", + "\n", + " except:\n", + " row['section_mode_argmax'] = np.nan\n", + " row['section_distance_argmax'] = np.nan\n", + " row['section_duration_argmax'] = np.nan\n", + "\n", + " finally:\n", + " return row\n", + " else:\n", + " \n", + " try:\n", + " distances = ast.literal_eval(row['section_distances'])\n", + " durations = ast.literal_eval(row['section_durations'])\n", + " modes = ast.literal_eval(row['section_modes'])\n", + "\n", + " argmax = np.argmax(distances)\n", + " \n", + " row['section_distance_argmax'] = distances[argmax]\n", + " row['section_duration_argmax'] = durations[argmax]\n", + " row['section_mode_argmax'] = modes[argmax]\n", + " \n", + " except:\n", + " row['section_mode_argmax'] = np.nan\n", + " row['section_distance_argmax'] = np.nan\n", + " row['section_duration_argmax'] = np.nan\n", + " \n", + " finally:\n", + " return row" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f0c008a3", + "metadata": {}, + "outputs": [], + "source": [ + "filtered_trips.reset_index(drop=True, inplace=True)" + ] + }, + { + "cell_type": "markdown", + "id": "7e1baa06", + "metadata": {}, + "source": [ + "### Available feature generation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "de49ec4f", + "metadata": {}, + "outputs": [], + "source": [ + "available = {\n", + " # AllCEO\n", + " 'Bicycle': 'p_micro',\n", + " 'Do not have vehicle': 'unknown',\n", + " 'Do not have vehicle ': 'unknown',\n", + " 'Get a ride from a friend or family member': 's_car',\n", + " 'None': 'no_trip',\n", + " 'Public transportation (bus, subway, light rail, etc.)': 'transit',\n", + " 'Rental car (including Zipcar/ Car2Go)': 'car',\n", + " 'Shared bicycle or scooter': 's_micro',\n", + " 'Skateboard': 'p_micro',\n", + " 'Taxi (regular taxi, Uber, Lyft, etc)': 'ridehail',\n", + " 'Walk/roll': 'walk',\n", + " 'Prefer not to say': 'unknown',\n", + " # Others\n", + " 'public_transportation__bus__subway__ligh': 'transit',\n", + " 'get_a_ride_from_a_friend_or_family_membe': 's_car', \n", + " 'bicycle': 'p_micro', \n", + " 'walk': 'walk',\n", + " 'taxi__regular_taxi__uber__lyft__etc': 'ridehail',\n", + " 'rental_car__including_zipcar__car2go': 'car', \n", + " 'prefer_not_to_say': 'unknown'\n", + "}\n", + "\n", + "# We use the sensed mode to update the available modes.\n", + "# This is to account for any user data input errors. E.g.: user does not select car as available mode\n", + "# but the sensed mode is car.\n", + "section_mode_mapping = {\n", + " 'bicycling': ['p_micro', 's_micro'],\n", + " 'car': ['s_car', 'car', 'ridehail'],\n", + " 'no_sensed': ['unknown'],\n", + " 'walking': ['walk'],\n", + " 'unknown': ['unknown'],\n", + " 'transit': ['transit']\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "62960039", + "metadata": {}, + "outputs": [], + "source": [ + "filtered_trips = filtered_trips.apply(lambda x: compute_argmax(CURRENT_DB, x), axis=1)\n", + "\n", + "# Drop all rows where argmax mode == air\n", + "filtered_trips.drop(\n", + " index=filtered_trips.loc[filtered_trips.section_mode_argmax.isin(['AIR_OR_HSR', 'air_or_hsr']),:].index, \n", + " inplace=True\n", + ")\n", + "\n", + "filtered_trips.section_mode_argmax.replace({\n", + " 'subway': 'transit',\n", + " 'no_sensed': 'unknown',\n", + " 'train': 'transit',\n", + " 'TRAM': 'transit',\n", + " 'LIGHT_RAIL': 'transit',\n", + " 'CAR': 'car',\n", + " 'WALKING': 'walking',\n", + " 'BICYCLING': 'bicycling',\n", + " 'UNKNOWN': 'unknown',\n", + " 'TRAIN': 'transit',\n", + " 'SUBWAY': 'transit',\n", + " 'BUS': 'transit',\n", + " 'bus': 'transit'\n", + "}, inplace=True)\n", + "\n", + "filtered_trips.dropna(subset='section_mode_argmax', inplace=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8583a709", + "metadata": {}, + "outputs": [], + "source": [ + "## Meters -> miles\n", + "filtered_trips['section_distance_argmax'] *= 0.000621371\n", + "\n", + "## Seconds -> minutes\n", + "filtered_trips['section_duration_argmax'] /= 60.\n", + "\n", + "## Total distance and duration are scaled too.\n", + "filtered_trips['distance'] *= 0.000621371\n", + "filtered_trips['duration'] /= 60." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1e4d05eb", + "metadata": {}, + "outputs": [], + "source": [ + "filtered_trips = filtered_trips.merge(right=filtered_survey, left_on='user_id_join', right_on='user_id_join')" + ] + }, + { + "cell_type": "markdown", + "id": "383fe251", + "metadata": {}, + "source": [ + "## Update available indicators" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ee097233", + "metadata": {}, + "outputs": [], + "source": [ + "import itertools\n", + "\n", + "new_cols = list(set(available.values()))\n", + "filtered_trips[new_cols] = 0\n", + "\n", + "for user_id, user_trips in filtered_trips.groupby('user_id'):\n", + " \n", + " if CURRENT_DB == \"Stage_database\":\n", + " \n", + " # Get the set of available modes (demographics.)\n", + " all_av_modes = user_trips['available_modes'].str.split(';').explode()\n", + " else:\n", + " # Get the set of available modes (demographics.)\n", + " all_av_modes = user_trips['available_modes'].str.split().explode()\n", + " \n", + " # Get all sensed modes.\n", + " all_sections = user_trips['section_mode_argmax'].unique()\n", + " \n", + " # Map to Common Normal Form.\n", + " mapped_sections = set(list(itertools.chain.from_iterable([section_mode_mapping[x] for x in all_sections])))\n", + " mapped_demo_av = set([available[x] for x in all_av_modes.unique()])\n", + " \n", + " # Perform a set union.\n", + " combined = list(mapped_sections.union(mapped_demo_av))\n", + " \n", + " # Update dummy indicators.\n", + " filtered_trips.loc[filtered_trips.user_id == user_id, combined] = 1\n", + "\n", + "filtered_trips.rename(columns=dict([(c, 'av_'+c) for c in new_cols]), inplace=True)" + ] + }, + { + "cell_type": "markdown", + "id": "38bfcc0c", + "metadata": {}, + "source": [ + "### Cost estimation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "054a6ad1", + "metadata": {}, + "outputs": [], + "source": [ + "# All values are taken from VTPI.\n", + "# https://www.vtpi.org/tca/tca0501.pdf\n", + "mode_cost_per_mile = {\n", + " # bicycle/skateboard\n", + " 'p_micro': 0.,\n", + " 'no_trip': 0.,\n", + " # Shared car is half the cost of regular car, which is $0.6/mile.\n", + " 's_car': 0.3,\n", + " # Rental car.\n", + " 'car': 0.6,\n", + " # Average of bus and train taken.\n", + " 'transit': 0.5,\n", + " # Shared bicyle or scooter - values taken from https://nacto.org/shared-micromobility-2020-2021/ and \n", + " # https://www.mckinsey.com/industries/automotive-and-assembly/our-insights/how-sharing-the-road-is-likely-to-transform-american-mobility\n", + " 's_micro': 0.3,\n", + " # uber/taxi/lyft\n", + " 'ridehail': 2.,\n", + " 'walk': 0.,\n", + " 'unknown': 0.\n", + "}\n", + "\n", + "# Assumptions.\n", + "mode_init_cost = {\n", + " 'p_micro': 0.,\n", + " 'no_trip': 0.,\n", + " # Shared car is half the cost of regular car, which is $0.6/mile.\n", + " 's_car': 0.,\n", + " # Rental car.\n", + " 'car': 0.,\n", + " # Average of bus and train taken.\n", + " 'transit': 0.,\n", + " # $1 unlocking cost.\n", + " 's_micro': 1.,\n", + " # uber/taxi/lyft\n", + " 'ridehail': 1.5,\n", + " 'walk': 0.,\n", + " 'unknown': 0.\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bccd3efb", + "metadata": {}, + "outputs": [], + "source": [ + "def compute_cost_estimates(df: pd.DataFrame):\n", + " \n", + " # Create some extra colums.\n", + " columns = [c.replace('av_', '') for c in df.columns if 'av_' in c]\n", + "\n", + " # Initialize the columns to 0.\n", + " df[columns] = 0.\n", + "\n", + " rows = list()\n", + "\n", + " # Iterate over every row.\n", + " for _, row in df.iterrows():\n", + " # Check which flags are active.\n", + " row_dict = row.to_dict()\n", + "\n", + " # Access the section_distance_argmax attribute for the distance. Note that this is now in miles.\n", + " distance = row_dict['section_distance_argmax']\n", + " \n", + " # Mask using availability.\n", + " for lookup in columns:\n", + " row_dict[lookup] = row_dict['av_' + lookup] * (\n", + " mode_init_cost[lookup] + (mode_cost_per_mile[lookup] * distance)\n", + " )\n", + "\n", + " rows.append(row_dict)\n", + "\n", + " new_df = pd.DataFrame(rows)\n", + " new_df.rename(columns=dict([(c, 'cost_'+c) for c in columns]), inplace=True)\n", + "\n", + " return new_df" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c39f1901", + "metadata": {}, + "outputs": [], + "source": [ + "filtered_trips = compute_cost_estimates(filtered_trips)" + ] + }, + { + "cell_type": "markdown", + "id": "a6c20466", + "metadata": {}, + "source": [ + "### Outlier removal" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c05071cc", + "metadata": {}, + "outputs": [], + "source": [ + "print(f\"For {CURRENT_DB=}, before outlier removal, n_rows = {filtered_trips.shape[0]}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b222715f", + "metadata": {}, + "outputs": [], + "source": [ + "# Drop instances where duration/distance is unusable.\n", + "filtered_trips.drop(\n", + " index=filtered_trips.loc[(filtered_trips.section_distance_argmax <= 0) | (filtered_trips.section_duration_argmax <= 0), :].index,\n", + " inplace=False\n", + ").reset_index(drop=True, inplace=True)\n", + "\n", + "\n", + "# bus, train, bicycling, walking, car\n", + "# split-apply-combine\n", + "def drop_outliers(df: pd.DataFrame, low=0.1, high=0.9) -> pd.DataFrame:\n", + " \n", + " def filter_by_percentiles(group):\n", + " distance_low = group['section_distance_argmax'].quantile(low)\n", + " distance_high = group['section_distance_argmax'].quantile(high)\n", + " duration_low = group['section_duration_argmax'].quantile(low)\n", + " duration_high = group['section_duration_argmax'].quantile(high)\n", + " \n", + " l1_filter = group[\n", + " (group['section_distance_argmax'] >= distance_low) &\n", + " (group['section_distance_argmax'] <= distance_high)\n", + " ].reset_index(drop=True)\n", + " \n", + " l2_filter = l1_filter[\n", + " (l1_filter['section_duration_argmax'] >= duration_low) &\n", + " (l1_filter['section_duration_argmax'] <= duration_high)\n", + " ].reset_index(drop=True)\n", + " \n", + " return l2_filter\n", + " \n", + " return df.groupby('section_mode_argmax').apply(filter_by_percentiles).reset_index(drop=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d77febb3", + "metadata": {}, + "outputs": [], + "source": [ + "filtered_trips = drop_outliers(filtered_trips, low=0.01, high=0.99)\n", + "\n", + "# Ideal speed. distance/time (in hours).\n", + "filtered_trips['mph'] = (\n", + " (filtered_trips['section_distance_argmax'] * 60.)/filtered_trips['section_duration_argmax']\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b52d5325", + "metadata": {}, + "outputs": [], + "source": [ + "filtered_trips[['section_mode_argmax', 'section_duration_argmax', 'section_distance_argmax', 'mph']].head(10)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c7ed953d", + "metadata": {}, + "outputs": [], + "source": [ + "def filter_mph(df: pd.DataFrame, low=0.1, high=0.9) -> pd.DataFrame:\n", + " \n", + " MPH_THRESHOLDS = {\n", + " # https://www.sciencedirect.com/science/article/pii/S2210670718304682\n", + " 'bicycling': 15.,\n", + " # https://www.ncbi.nlm.nih.gov/pmc/articles/PMC7806575/\n", + " 'walking': 2.93\n", + " }\n", + " \n", + " def custom_filter(group):\n", + " # Drop data specified in the dict manually.\n", + " if group.name in MPH_THRESHOLDS.keys():\n", + " f_df = group[group['mph'] <= MPH_THRESHOLDS[group.name]]\n", + " else:\n", + " mph_low = group['mph'].quantile(low)\n", + " mph_high = group['mph'].quantile(high)\n", + "\n", + " f_df = group[(group['mph'] >= mph_low) & (group['mph'] <= mph_high)]\n", + " \n", + " return f_df\n", + " \n", + " return df.groupby('section_mode_argmax').apply(custom_filter).reset_index(drop=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0c1904cd", + "metadata": {}, + "outputs": [], + "source": [ + "filtered_trips = filter_mph(filtered_trips, low=0.01, high=0.99)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3dce2b1c", + "metadata": {}, + "outputs": [], + "source": [ + "filtered_trips.groupby('section_mode_argmax')[['section_distance_argmax', 'section_duration_argmax']].describe()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "396f196b", + "metadata": {}, + "outputs": [], + "source": [ + "filtered_trips.groupby('section_mode_argmax')[['mph']].describe()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "41109148", + "metadata": {}, + "outputs": [], + "source": [ + "print(f\"For {CURRENT_DB=}, After outlier removal, n_rows = {filtered_trips.shape[0]}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1ca22a08", + "metadata": {}, + "outputs": [], + "source": [ + "to_drop=[\n", + " '_id', 'additions', 'cleaned_section_summary', 'cleaned_trip', 'confidence_threshold', \n", + " 'end_fmt_time', 'end_loc', 'end_local_dt_day', 'raw_trip', 'purpose_confirm',\n", + " 'end_local_dt_minute', 'end_local_dt_month', 'end_local_dt_second', 'end_local_dt_timezone', \n", + " 'end_local_dt_weekday', 'end_local_dt_year', 'end_place', 'end_ts', 'expectation', 'expected_trip', \n", + " 'inferred_labels', 'inferred_section_summary', 'inferred_trip', 'metadata_write_ts', 'mode_confirm', \n", + " 'section_durations', 'section_modes', 'source', 'start_fmt_time', 'start_loc', 'start_local_dt_day', \n", + " 'start_local_dt_minute', 'start_local_dt_month', 'start_local_dt_second', \n", + " 'start_local_dt_timezone', 'start_local_dt_weekday', 'start_local_dt_year', 'start_place', \n", + " 'start_ts', 'user_id_join', 'user_input', 'survey_user_id', 'section_distances',\n", + " 'data.local_dt.year', 'data.local_dt.month', 'data.local_dt.day', 'data.local_dt.hour', \n", + " 'data.local_dt.minute', 'data.local_dt.second', 'data.local_dt.weekday', 'data.local_dt.timezone',\n", + " 'data.fmt_time'\n", + "]\n", + "\n", + "for col in to_drop:\n", + " if col in filtered_trips.columns:\n", + " filtered_trips.drop(columns=[col], inplace=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2937d4ef", + "metadata": {}, + "outputs": [], + "source": [ + "filtered_trips.rename({'start_local_dt_hour': 'start:hour', 'end_local_dt_hour': 'end:hour'}, inplace=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "87c7fc92", + "metadata": {}, + "outputs": [], + "source": [ + "print(filtered_trips.columns.tolist())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9ea36cad", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "display(filtered_trips.head())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a7018bf4", + "metadata": {}, + "outputs": [], + "source": [ + "print(f\"Done processing for {CURRENT_DB=}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0eacc539", + "metadata": {}, + "outputs": [], + "source": [ + "targets = ['p_micro', 'no_trip', 's_car', 'transit', 'car', 's_micro', 'ridehail', 'walk', 'unknown']\n", + "\n", + "# Rename and map targets.\n", + "filtered_trips.rename(columns={'replaced_mode': 'target'}, inplace=True)\n", + "filtered_trips.replace({'target': {t: ix+1 for ix, t in enumerate(targets)}}, inplace=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "50d3eaec", + "metadata": {}, + "outputs": [], + "source": [ + "display(filtered_trips.target.unique())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "31f35a04", + "metadata": {}, + "outputs": [], + "source": [ + "filtered_trips.to_csv(f'../data/filtered_data/preprocessed_data_{CURRENT_DB}.csv', index=False)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "emission", + "language": "python", + "name": "emission" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.16" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/rm_src/02_run_trip_level_models.py b/rm_src/02_run_trip_level_models.py new file mode 100644 index 0000000..16567ec --- /dev/null +++ b/rm_src/02_run_trip_level_models.py @@ -0,0 +1,491 @@ +from enum import Enum +import random +import warnings +import argparse +from pathlib import Path +from collections import Counter + +# Math and graphing. +import pandas as pd +import numpy as np +import seaborn as sns +import matplotlib.pyplot as plt + +# sklearn imports. +from sklearn.model_selection import train_test_split +from sklearn.preprocessing import StandardScaler +from sklearn.linear_model import LinearRegression +from sklearn.metrics import f1_score, r2_score, ConfusionMatrixDisplay +from scipy.special import kl_div +from sklearn.metrics import classification_report +from sklearn.model_selection import GridSearchCV, StratifiedGroupKFold +from pprint import pprint +from sklearn.inspection import permutation_importance +from time import perf_counter +from sklearn.ensemble import RandomForestClassifier + +warnings.simplefilter(action='ignore', category=Warning) + +# Global experiment flags and variables. +SEED = 13210 +TARGETS = ['p_micro', 'no_trip', 's_car', 'transit', 'car', 's_micro', 'ridehail', 'walk', 'unknown'] +MAP = {ix+1:t for ix, t in enumerate(TARGETS)} + +CV = False + +# Set the Numpy seed too. +random.seed(SEED) +np.random.seed(SEED) + +class SPLIT_TYPE(Enum): + INTRA_USER = 0 + INTER_USER = 1 + TARGET = 2 + MODE = 3 + HIDE_USER = 4 + + +class SPLIT(Enum): + TRAIN = 0 + TEST = 1 + + +def get_train_test_splits(data: pd.DataFrame, how=SPLIT_TYPE, test_ratio=0.2, shuffle=True): + + if how == SPLIT_TYPE.INTER_USER: + + X = data.drop(columns=['target']) + y = data['target'].values + groups = data.user_id.values + + # n_splits determines split size. So n=5, is 20% for each split, which is what we want. + splitter = StratifiedGroupKFold(n_splits=5, shuffle=shuffle, random_state=SEED) + # splitter = GroupKFold(n_splits=5) + + for train_index, test_index in splitter.split(X, y, groups): + X_tr = data.iloc[train_index, :] + X_te = data.iloc[test_index, :] + + # Iterate only once and break. + break + + return X_tr, X_te, None + + elif how == SPLIT_TYPE.INTRA_USER: + + # There are certain users with only one observation. What do we do with those? + # As per the mobilitynet modeling pipeline, we randomly assign them to either the + # training or test set. + + value_counts = data.user_id.value_counts() + single_count_ids = value_counts[value_counts == 1].index + + data_filtered = data.loc[~data.user_id.isin(single_count_ids), :].reset_index(drop=True) + data_single_counts = data.loc[data.user_id.isin(single_count_ids), :].reset_index(drop=True) + + X_tr, X_te = train_test_split( + data_filtered, test_size=test_ratio, shuffle=shuffle, stratify=data_filtered.user_id, + random_state=SEED + ) + + data_single_counts['assigned'] = np.random.choice(['train', 'test'], len(data_single_counts)) + X_tr_merged = pd.concat( + [X_tr, data_single_counts.loc[data_single_counts.assigned == 'train', :].drop( + columns=['assigned'], inplace=False + )], + ignore_index=True, axis=0 + ) + + X_te_merged = pd.concat( + [X_te, data_single_counts.loc[data_single_counts.assigned == 'test', :].drop( + columns=['assigned'], inplace=False + )], + ignore_index=True, axis=0 + ) + + return X_tr_merged, X_te_merged, None + + elif how == SPLIT_TYPE.TARGET: + + X_tr, X_te = train_test_split( + data, test_size=test_ratio, shuffle=shuffle, stratify=data.target, + random_state=SEED + ) + + return X_tr, X_te, None + + elif how == SPLIT_TYPE.MODE: + X_tr, X_te = train_test_split( + data, test_size=test_ratio, shuffle=shuffle, stratify=data.section_mode_argmax, + random_state=SEED + ) + + return X_tr, X_te, None + + + elif how == SPLIT_TYPE.HIDE_USER: + users = data.user_id.value_counts(normalize=True) + percentiles = users.quantile([0.25, 0.5, 0.75]) + + low_trip_users = users[users <= percentiles[0.25]].index + mid_trip_users = users[(percentiles[0.25] <= users) & (users <= percentiles[0.5])].index + high_trip_users = users[(percentiles[0.5] <= users) & (users <= percentiles[0.75])].index + + # select one from each randomly. + user1 = np.random.choice(low_trip_users) + user2 = np.random.choice(mid_trip_users) + user3 = np.random.choice(high_trip_users) + + print(f"Users picked: {user1}, {user2}, {user3}") + + # Remove these users from the entire dataset. + held_out = data.loc[data.user_id.isin([user1, user2, user3]), :].reset_index(drop=True) + remaining = data.loc[~data.user_id.isin([user1, user2, user3]), :].reset_index(drop=True) + + # Split randomly. + X_tr, X_te = train_test_split( + remaining, test_size=test_ratio, shuffle=shuffle, random_state=SEED + ) + + return X_tr, X_te, held_out + + raise NotImplementedError("Unknown split type") + + +def get_duration_estimate(df: pd.DataFrame, dset: SPLIT, model_dict: dict): + + X_features = ['section_distance_argmax', 'mph'] + + if dset == SPLIT.TRAIN and model_dict is None: + model_dict = dict() + + if dset == SPLIT.TEST and model_dict is None: + raise AttributeError("Expected model dict for testing.") + + if dset == SPLIT.TRAIN: + for section_mode in df.section_mode_argmax.unique(): + section_data = df.loc[df.section_mode_argmax == section_mode, :] + if section_mode not in model_dict: + model_dict[section_mode] = dict() + + model = LinearRegression(fit_intercept=True) + + X = section_data[X_features] + Y = section_data[['section_duration_argmax']] + + model.fit(X, Y.values.ravel()) + + r2 = r2_score(y_pred=model.predict(X), y_true=Y.values.ravel()) + print(f"\t-> Train R2 for {section_mode}: {r2}") + + model_dict[section_mode]['model'] = model + + elif dset == SPLIT.TEST: + for section_mode in df.section_mode_argmax.unique(): + section_data = df.loc[df.section_mode_argmax == section_mode, :] + X = section_data[X_features] + Y = section_data[['section_duration_argmax']] + + y_pred = model_dict[section_mode]['model'].predict(X) + r2 = r2_score(y_pred=y_pred, y_true=Y.values.ravel()) + print(f"\t-> Test R2 for {section_mode}: {r2}") + + # Create the new columns for the duration. + new_columns = ['p_micro','no_trip','s_car','transit','car','s_micro','ridehail','walk','unknown'] + df[TARGETS] = 0 + df['temp'] = 0 + + for section in df.section_mode_argmax.unique(): + X_section = df.loc[df.section_mode_argmax == section, X_features] + + # broadcast to all columns. + df.loc[df.section_mode_argmax == section, 'temp'] = model_dict[section]['model'].predict(X_section) + + for c in TARGETS: + df[c] = df['av_' + c] * df['temp'] + + df.drop(columns=['temp'], inplace=True) + + df.rename(columns=dict([(x, 'tt_'+x) for x in TARGETS]), inplace=True) + + # return model_dict, result_df + return model_dict, df + +# Some helper functions that will help ease redundancy in the code. + +def drop_columns(df: pd.DataFrame): + to_drop = ['section_mode_argmax', 'available_modes', 'user_id'] + + # Drop section_mode_argmax and available_modes. + return df.drop( + columns=to_drop, + inplace=False + ) + + +def scale_values(df: pd.DataFrame, split: SPLIT, scalers=None): + # Scale costs using StandardScaler. + costs = df[[c for c in df.columns if 'cost_' in c]].copy() + times = df[[c for c in df.columns if 'tt_' in c or 'duration' in c]].copy() + distances = df[[c for c in df.columns if 'distance' in c or 'mph' in c]].copy() + + print( + "Cost columns to be scaled: ", costs.columns,"\nTime columns to be scaled: ", times.columns, \ + "\nDistance columns to be scaled: ", distances.columns + ) + + if split == SPLIT.TRAIN and scalers is None: + cost_scaler = StandardScaler() + tt_scaler = StandardScaler() + dist_scaler = StandardScaler() + + cost_scaled = pd.DataFrame( + cost_scaler.fit_transform(costs), + columns=costs.columns, + index=costs.index + ) + + tt_scaled = pd.DataFrame( + tt_scaler.fit_transform(times), + columns=times.columns, + index=times.index + ) + + dist_scaled = pd.DataFrame( + dist_scaler.fit_transform(distances), + columns=distances.columns, + index=distances.index + ) + + elif split == SPLIT.TEST and scalers is not None: + + cost_scaler, tt_scaler, dist_scaler = scalers + + cost_scaled = pd.DataFrame( + cost_scaler.transform(costs), + columns=costs.columns, + index=costs.index + ) + + tt_scaled = pd.DataFrame( + tt_scaler.transform(times), + columns=times.columns, + index=times.index + ) + + dist_scaled = pd.DataFrame( + dist_scaler.transform(distances), + columns=distances.columns, + index=distances.index + ) + + else: + raise NotImplementedError("Unknown split") + + # Drop the original columns. + df.drop( + columns=costs.columns.tolist() + times.columns.tolist() + distances.columns.tolist(), + inplace=True + ) + + df = df.merge(right=cost_scaled, left_index=True, right_index=True) + df = df.merge(right=tt_scaled, left_index=True, right_index=True) + df = df.merge(right=dist_scaled, left_index=True, right_index=True) + + return df, (cost_scaler, tt_scaler, dist_scaler) + + +def train(X_tr, Y_tr): + if CV: + + model = RandomForestClassifier(random_state=SEED) + + # We want to build bootstrapped trees that would not always use all the features. + param_set2 = { + 'n_estimators': [150, 200, 250], + 'min_samples_split': [2, 3, 4], + 'min_samples_leaf': [1, 2, 3], + 'class_weight': ['balanced_subsample'], + 'max_features': [None, 'sqrt'], + 'bootstrap': [True] + } + + cv_set2 = StratifiedKFold(n_splits=3, shuffle=True, random_state=SEED) + + clf_set2 = GridSearchCV(model, param_set2, cv=cv_set2, n_jobs=-1, scoring='f1_weighted', verbose=1) + + start = perf_counter() + + clf_set2.fit( + X_tr, + Y_tr + ) + + time_req = (perf_counter() - start)/60. + + best_model = clf_set2.best_estimator_ + else: + best_model = RandomForestClassifier( + n_estimators=150, + max_depth=None, + min_samples_leaf=2, + bootstrap=True, + class_weight='balanced_subsample', + random_state=SEED, + n_jobs=-1 + ).fit(X_tr, Y_tr) + + return best_model + + +def predict(model, X_tr, Y_tr, X_te, Y_te): + + y_test_pred = model.predict(X_te) + y_train_pred = model.predict(X_tr) + + train_f1 = f1_score( + y_true=Y_tr, + y_pred=y_train_pred, + average='weighted', + zero_division=0. + ) + + test_f1 = f1_score( + y_true=Y_te, + y_pred=y_test_pred, + average='weighted', + zero_division=0. + ) + + return y_train_pred, train_f1, y_test_pred, test_f1 + + +def run_sampled_sweep(df: pd.DataFrame, dir_name: Path, **kwargs): + + targets = TARGETS.copy() + + split = kwargs.pop('split', None) + + try: + train_data, test_data, hidden_data = get_train_test_splits(data=df, how=split, shuffle=True) + except Exception as e: + print(e) + return + + params, train_data = get_duration_estimate(train_data, SPLIT.TRAIN, None) + _, test_data = get_duration_estimate(test_data, SPLIT.TEST, params) + + train_data = drop_columns(train_data) + test_data = drop_columns(test_data) + + X_tr, Y_tr = train_data.drop(columns=['target'], inplace=False), train_data.target.values.ravel() + X_te, Y_te = test_data.drop(columns=['target'], inplace=False), test_data.target.values.ravel() + + model = train(X_tr, Y_tr) + tr_preds, tr_f1, te_preds, te_f1 = predict(model, X_tr, Y_tr, X_te, Y_te) + + print(f"\t-> Train F1: {tr_f1}, Test F1: {te_f1}") + + importance = sorted( + zip( + model.feature_names_in_, + model.feature_importances_ + ), + key=lambda x: x[-1], reverse=True + ) + + with open(dir_name / 'f1_scores.txt', 'w') as f: + f.write(f"Train F1: {tr_f1}\nTest F1: {te_f1}") + + importance_df = pd.DataFrame(importance, columns=['feature_name', 'importance']) + importance_df.to_csv(dir_name / 'feature_importance.csv', index=False) + + # target_names = [MAP[x] for x in np.unique(Y_te)] + + with open(dir_name / 'classification_report.txt', 'w') as f: + f.write(classification_report(y_true=Y_te, y_pred=te_preds)) + + if split == SPLIT_TYPE.HIDE_USER and hidden_data is not None: + _, hidden_data = get_duration_estimate(hidden_data, SPLIT.TEST, params) + hidden_data = drop_columns(hidden_data) + + X_hid, Y_hid = hidden_data.drop(columns=['target'], inplace=False), hidden_data.target.values.ravel() + + tr_preds, tr_f1, te_preds, te_f1 = predict(model, X_tr, Y_tr, X_hid, Y_hid) + print(f"\t\t ---> Hidden user F1: {te_f1} <---") + + fig, ax = plt.subplots(figsize=(7, 7)) + cm = ConfusionMatrixDisplay.from_estimator( + model, + X=X_te, + y=Y_te, + ax=ax + ) + # ax.set_xticklabels(target_names, rotation=45) + # ax.set_yticklabels(target_names) + fig.tight_layout() + plt.savefig(dir_name / 'test_confusion_matrix.png') + plt.close('all') + + +def save_metadata(dir_name: Path, **kwargs): + with open(dir_name / 'metadata.txt', 'w') as f: + for k, v in kwargs.items(): + f.write(f"{k}: {v}\n") + + + +if __name__ == "__main__": + + datasets = sorted(list(Path('../data/filtered_data').glob('preprocessed_data_*.csv'))) + + start = perf_counter() + + for dataset in datasets: + name = dataset.name.replace('.csv', '') + + print(f"Starting modeling for dataset = {name}") + + data = pd.read_csv(dataset) + data.drop_duplicates(inplace=True) + data.dropna(inplace=True) + + if 'deprecatedID' in data.columns: + data.drop(columns=['deprecatedID'], inplace=True) + if 'data.key' in data.columns: + data.drop(columns=['data.key'], inplace=True) + + # These two lines make all the difference. + data.sort_values(by=['user_id'], ascending=True, inplace=True) + data = data[sorted(data.columns.tolist())] + + print("Beginning sweeps.") + + # args = parse_args() + sweep_number = 1 + + root = Path('../outputs/benchmark_results') + if not root.exists(): + root.mkdir() + + for split in [SPLIT_TYPE.INTER_USER, SPLIT_TYPE.INTRA_USER, SPLIT_TYPE.TARGET, SPLIT_TYPE.MODE, SPLIT_TYPE.HIDE_USER]: + kwargs = { + 'dataset': name, + 'split': split + } + + dir_name = root / f'benchmark_{name}_{sweep_number}' + + if not dir_name.exists(): + dir_name.mkdir() + + print(f"\t-> Running sweep #{sweep_number} with metadata={str(kwargs)}") + save_metadata(dir_name, **kwargs) + run_sampled_sweep(data.copy(), dir_name, **kwargs) + print(f"Completed benchmarking for {sweep_number} experiment.") + print(50*'-') + sweep_number += 1 + + elapsed = perf_counter() - start + + print(f"Completed sweeps in {elapsed/60.} minutes") \ No newline at end of file diff --git a/rm_src/03_user_level_models.ipynb b/rm_src/03_user_level_models.ipynb new file mode 100644 index 0000000..9a31729 --- /dev/null +++ b/rm_src/03_user_level_models.ipynb @@ -0,0 +1,2743 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "04ccf092", + "metadata": {}, + "source": [ + "## Some important points to remember:\n", + "\n", + "### We want to experiment with two types of models:\n", + "\n", + "\n", + "1. have one row per user, so that when predicting modes for a new user, we pick the \"similar user\" or users and determine the replaced mode\n", + " - In this, the traditional approach would only use demographics for the user features, we may experiment with some summaries of the trip data that will function as some level of \"fingerprint\" for the user. Ideally we would be able to show that this performs better than demographics alone\n", + " - Note also that the original method that you had outlined where the training set is a list of trips (O()) is a third approach which we will be comparing these two against" + ] + }, + { + "cell_type": "markdown", + "id": "c0c1ee88", + "metadata": {}, + "source": [ + "Target order:\n", + "\n", + "```\n", + "['p_micro', 'no_trip', 's_car', 'transit', 'car', 's_micro', 'ridehail', 'walk', 'unknown']\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "21ef0f2e", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import numpy as np\n", + "import random\n", + "import os\n", + "import pickle\n", + "import ast\n", + "import matplotlib.pyplot as plt\n", + "import seaborn as sns\n", + "\n", + "from sklearn.linear_model import LinearRegression\n", + "from sklearn.ensemble import RandomForestClassifier\n", + "from sklearn.metrics import r2_score, f1_score, log_loss\n", + "from sklearn.model_selection import train_test_split, RandomizedSearchCV, StratifiedKFold, KFold\n", + "from sklearn.neighbors import KNeighborsClassifier\n", + "from sklearn.cluster import KMeans\n", + "from sklearn.metrics.pairwise import cosine_similarity, euclidean_distances\n", + "from enum import Enum\n", + "from scipy.stats import uniform\n", + "from typing import List, Dict, Union\n", + "from pandas.api.types import is_numeric_dtype\n", + "from sklearn.manifold import TSNE\n", + "from multiprocessing import cpu_count\n", + "\n", + "pd.set_option('display.max_columns', 100)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "fef98692", + "metadata": {}, + "outputs": [], + "source": [ + "SEED = 13210\n", + "\n", + "np.random.seed(SEED)\n", + "random.seed(SEED)\n", + "\n", + "SimilarityMetric = Enum('SimilarityMetric', ['COSINE', 'EUCLIDEAN', 'KNN', 'KMEANS'])\n", + "GroupType = Enum('GroupType', ['GROUPBY', 'CUT'])" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "79f8c51a", + "metadata": {}, + "outputs": [], + "source": [ + "df = pd.read_csv('../data/filtered_data/preprocessed_data_Stage_database.csv')\n", + "# df = pd.read_csv('../data/filtered_data/preprocessed_data_openpath_prod_durham.csv')\n", + "# df = pd.read_csv('../data/filtered_data/preprocessed_data_openpath_prod_mm_masscec.csv')\n", + "# df = pd.read_csv('../data/filtered_data/preprocessed_data_openpath_prod_ride2own.csv')\n", + "# df = pd.read_csv('../data/filtered_data/preprocessed_data_openpath_prod_uprm_nicr.csv')" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "915e9d6f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([1, 9, 5, 8, 3, 2, 4])" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df.groupby('user_id')['target'].apply(lambda x: x.value_counts().idxmax()).unique()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "72793473", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['distance', 'duration', 'end_local_dt_hour', 'target', 'section_distance_argmax', 'section_duration_argmax', 'section_mode_argmax', 'start_local_dt_hour', 'user_id', 'has_drivers_license', 'is_student', 'is_paid', 'income_category', 'n_residence_members', 'n_residents_u18', 'n_residents_with_license', 'n_motor_vehicles', 'available_modes', 'has_medical_condition', 'ft_job', 'multiple_jobs', 'n_working_residents', \"highest_education_Bachelor's degree\", 'highest_education_Graduate degree or professional degree', 'highest_education_High school graduate or GED', 'highest_education_Less than a high school graduate', 'highest_education_Prefer not to say', 'highest_education_Some college or associates degree', 'primary_job_description_Clerical or administrative support', 'primary_job_description_Custodial', 'primary_job_description_Education', 'primary_job_description_Food service', 'primary_job_description_Linecook', 'primary_job_description_Manufacturing, construction, maintenance, or farming', 'primary_job_description_Medical/healthcare', 'primary_job_description_Non-profit program manager', 'primary_job_description_Other', 'primary_job_description_Professional, managerial, or technical', 'primary_job_description_Sales or service', 'primary_job_description_Self employed', 'primary_job_description_food service', 'gender_Man', 'gender_Nonbinary/genderqueer/genderfluid', 'gender_Prefer not to say', 'gender_Woman', 'gender_Woman;Nonbinary/genderqueer/genderfluid', 'age_16___20_years_old', 'age_21___25_years_old', 'age_26___30_years_old', 'age_31___35_years_old', 'age_36___40_years_old', 'age_41___45_years_old', 'age_46___50_years_old', 'age_51___55_years_old', 'age_56___60_years_old', 'age_61___65_years_old', 'age___65_years_old', 'av_transit', 'av_no_trip', 'av_p_micro', 'av_s_micro', 'av_ridehail', 'av_unknown', 'av_walk', 'av_car', 'av_s_car', 'cost_transit', 'cost_no_trip', 'cost_p_micro', 'cost_s_micro', 'cost_ridehail', 'cost_unknown', 'cost_walk', 'cost_car', 'cost_s_car', 'mph']\n" + ] + } + ], + "source": [ + "print(df.columns.tolist())" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "765f08ff", + "metadata": {}, + "outputs": [], + "source": [ + "def generate_tsne_plots(df: pd.DataFrame, **kwargs):\n", + " \n", + " df = df.copy()\n", + " \n", + " # Important - if not cast as a category, seaborn considers this as a numerical value.\n", + " df.target = df.target.astype('category')\n", + " \n", + " # print(\"Unique targets: \", df.target.unique())\n", + " \n", + " # According to the docs, > consider choosing a perplexity between 5 and 50.\n", + " tsne = TSNE(\n", + " n_components=2,\n", + " perplexity=kwargs.pop('perplexity', 5),\n", + " n_iter=kwargs.pop('n_iter', 2000),\n", + " metric=kwargs.pop('metric', 'cosine'),\n", + " random_state=SEED,\n", + " n_jobs=os.cpu_count()\n", + " )\n", + " \n", + " if df.index.name == 'user_id':\n", + " df.reset_index(drop=False, inplace=True)\n", + " \n", + " if 'user_id' in df.columns:\n", + " df.drop(columns=['user_id'], inplace=True)\n", + " \n", + " targets = df.target.values\n", + " df.drop(columns=['target'], inplace=True)\n", + " \n", + " projected = tsne.fit_transform(df)\n", + " \n", + " fig, ax = plt.subplots()\n", + " sns.scatterplot(x=projected[:, 0], y=projected[:, 1], hue=targets, ax=ax)\n", + " ax.set(xlabel='Embedding dimension 1', ylabel='Embedding dimension 2', title='t-SNE plot for data')\n", + " plt.show()\n", + " \n", + " return projected" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "cfe76e8c", + "metadata": {}, + "outputs": [], + "source": [ + "def get_mode_coverage(df: pd.DataFrame):\n", + " \n", + " coverage_df = df.groupby(['user_id', 'section_mode_argmax']).size().unstack(fill_value=0)\n", + " coverage_df.columns = ['coverage_' + str(c) for c in coverage_df.columns]\n", + " \n", + " # As a preventative measure.\n", + " coverage_df.fillna(0, inplace=True)\n", + " \n", + " # Normalize over rows.\n", + " coverage_df.iloc[:, 1:] = coverage_df.iloc[:, 1:].div(coverage_df.iloc[:, 1:].sum(axis=1), axis=0)\n", + " \n", + " return coverage_df" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "75313008", + "metadata": {}, + "outputs": [], + "source": [ + "def get_trip_summaries(df: pd.DataFrame, group_key: str, feature_list: List[str], **kwargs):\n", + " \n", + " def get_feature_summaries(trip_feature: str, is_ordinal: bool = False):\n", + " \n", + " if is_numeric_dtype(df[group_key]):\n", + " col_prefix = f'{trip_feature}_mean_cut'\n", + " if not use_qcut:\n", + " grouper = df.groupby(['user_id', pd.cut(df[group_key], n_cuts)])[trip_feature]\n", + " else:\n", + " grouper = df.groupby(['user_id', pd.qcut(df[group_key], n_cuts)])[trip_feature]\n", + " else:\n", + " grouper = df.groupby(['user_id', group_key])[trip_feature]\n", + " \n", + " if not is_ordinal:\n", + " # A mean of 0 is an actual value.\n", + " \n", + " mean = grouper.mean().unstack(level=-1, fill_value=-1.)\n", + " \n", + " mean.columns = [f'{trip_feature}_mean_' + str(c) for c in mean.columns]\n", + " \n", + " # Same with percentiles - 0 is an actual value.\n", + " median = grouper.median().unstack(level=-1, fill_value=-1.)\n", + " median.columns = [f'{trip_feature}_median_' + str(c) for c in median.columns]\n", + " \n", + " iqr_df = grouper.quantile([0.25, 0.75]).unstack(level=-1)\n", + " iqr = (iqr_df[0.75] - iqr_df[0.25]).unstack(level=-1)\n", + " iqr.fillna(-1., inplace=True)\n", + " iqr.columns = [f'{trip_feature}_iqr_' + str(c) for c in iqr.columns]\n", + "\n", + " # Now merge.\n", + " merged = mean.copy()\n", + " merged = merged.merge(right=median, left_index=True, right_index=True)\n", + " merged = merged.merge(right=iqr, left_index=True, right_index=True)\n", + " \n", + " merged.fillna(-1., inplace=True)\n", + "\n", + " return merged\n", + " \n", + " # 0 is OK to indicate NaN values.\n", + " f_mode = grouper.apply(\n", + " lambda x: x.value_counts().idxmax()\n", + " ).unstack(fill_value=0.)\n", + " \n", + " f_mode.columns = [f'{trip_feature}_mode_' + str(c) for c in f_mode.columns]\n", + " f_mode.fillna(0., inplace=True)\n", + " \n", + " return f_mode\n", + " \n", + " assert group_key not in feature_list, \"Cannot perform grouping and summarization of the same feature.\"\n", + " \n", + " # Optional kwarg for number of cuts for numeric dtype grouping.\n", + " # Default is 3: short, medium, long trip types:\n", + " # For e.g., if the group key is 'section_duration', it will be cut into three equally-sized bins,\n", + " # However, an alternative is also present - we could use qcut() instead, which would ensure that\n", + " # each bin has roughly the same number of samples.\n", + " n_cuts = kwargs.pop('n_cuts', 3)\n", + " use_qcut = kwargs.pop('use_qcut', False)\n", + " \n", + " # This will be the dataframe that all subsequent features will join to.\n", + " feature_df = None\n", + " \n", + " for ix, feature in enumerate(feature_list):\n", + " is_ordinal = feature == 'start_local_dt_hour' or feature == 'end_local_dt_hour'\n", + " if ix == 0:\n", + " feature_df = get_feature_summaries(feature, is_ordinal)\n", + " else:\n", + " next_feature_df = get_feature_summaries(feature, is_ordinal)\n", + " feature_df = feature_df.merge(right=next_feature_df, left_index=True, right_index=True)\n", + " \n", + " return feature_df" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "63617ada", + "metadata": {}, + "outputs": [], + "source": [ + "def get_demographic_data(df: pd.DataFrame, **trip_kwargs):\n", + " \n", + " '''\n", + " A method that returns a U x (D + t) matrix, where U = number of users,\n", + " D = number of demographic features, t (optional) = number of trip summary features.\n", + " \n", + " When use_trip_summaries=True, the 'available_modes' column is dropped in favor of\n", + " the already-preprocessed av_ columns. This is because we want to incorporate trip-level\n", + " information into the data. When the argument is False, we want to SOLELY use demographics.\n", + " '''\n", + " \n", + " trip_features_to_use = trip_kwargs.pop('trip_features', None)\n", + " trip_group_key = trip_kwargs.pop('trip_grouping', 'section_mode_argmax')\n", + " \n", + " demographics = [ \n", + " 'has_drivers_license', 'is_student', 'is_paid', 'income_category', 'n_residence_members', \n", + " 'n_residents_u18', 'n_residents_with_license', 'n_motor_vehicles',\n", + " 'has_medical_condition', 'ft_job', 'multiple_jobs', 'n_working_residents', \n", + " \"highest_education_Bachelor's degree\", 'highest_education_Graduate degree or professional degree', \n", + " 'highest_education_High school graduate or GED', 'highest_education_Less than a high school graduate', \n", + " 'highest_education_Prefer not to say', 'highest_education_Some college or associates degree', \n", + " 'primary_job_description_Clerical or administrative support', 'primary_job_description_Custodial', \n", + " 'primary_job_description_Education', 'primary_job_description_Food service', \n", + " 'primary_job_description_Linecook', \n", + " 'primary_job_description_Manufacturing, construction, maintenance, or farming', \n", + " 'primary_job_description_Medical/healthcare', 'primary_job_description_Non-profit program manager', \n", + " 'primary_job_description_Other', 'primary_job_description_Professional, managerial, or technical', \n", + " 'primary_job_description_Sales or service', 'primary_job_description_Self employed', \n", + " 'primary_job_description_food service', 'gender_Man', 'gender_Nonbinary/genderqueer/genderfluid', \n", + " 'gender_Prefer not to say', 'gender_Woman', 'gender_Woman;Nonbinary/genderqueer/genderfluid', \n", + " 'age_16___20_years_old', 'age_21___25_years_old', 'age_26___30_years_old', 'age_31___35_years_old', \n", + " 'age_36___40_years_old', 'age_41___45_years_old', 'age_46___50_years_old', 'age_51___55_years_old', \n", + " 'age_56___60_years_old', 'age_61___65_years_old', 'age___65_years_old', 'av_transit', 'av_no_trip', \n", + " 'av_p_micro', 'av_s_micro', 'av_ridehail', 'av_unknown', 'av_walk', 'av_car', 'av_s_car', \n", + " ]\n", + " \n", + " # Retain only the first instance of each user and subset the columns.\n", + " filtered = df.groupby('user_id').first()[demographics]\n", + " \n", + " # Get the targets.\n", + " targets = df.groupby('user_id')['target'].apply(lambda x: x.value_counts().idxmax())\n", + " \n", + " filtered = filtered.merge(right=targets, left_index=True, right_index=True)\n", + " \n", + " if trip_features_to_use is None or len(trip_features_to_use) == 0:\n", + "# # Use the available modes as indicators.\n", + "# return encode_availability(filtered)\n", + " return filtered\n", + " \n", + " # -----------------------------------------------------------\n", + " # Reaching here means that we need to include trip summaries\n", + " # -----------------------------------------------------------\n", + " \n", + " # If trip summaries are to be used, then re-use the preprocessed availability features.\n", + " availability = df[['user_id'] + [c for c in df.columns if 'av_' in c]]\n", + " availability = availability.groupby('user_id').first()\n", + " \n", + " # For every user, generate the global trip-level summaries.\n", + " global_aggs = df.groupby('user_id').agg({'duration': 'mean', 'distance': 'mean'})\n", + " \n", + " # coverage.\n", + " coverage = get_mode_coverage(df)\n", + " \n", + " # Trip-level features.\n", + " trip_features = get_trip_summaries(\n", + " df=df, \n", + " group_key=trip_group_key, \n", + " feature_list=trip_features_to_use,\n", + " use_qcut=trip_kwargs.pop('use_qcut', False)\n", + " )\n", + " \n", + " targets = df.groupby('user_id')['target'].apply(lambda x: x.value_counts().idxmax())\n", + " \n", + " trip_features = trip_features.merge(right=coverage, left_index=True, right_index=True)\n", + " trip_features = trip_features.merge(right=global_aggs, left_index=True, right_index=True)\n", + " \n", + " # Finally, join with availability indicators and targets.\n", + " trip_features = trip_features.merge(right=availability, left_index=True, right_on='user_id')\n", + " trip_features = trip_features.merge(right=targets, left_index=True, right_index=True)\n", + " \n", + " return trip_features.reset_index(drop=False)" + ] + }, + { + "cell_type": "markdown", + "id": "fedb51e8", + "metadata": {}, + "source": [ + "## Experiment 1: Only demographics" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "66421120", + "metadata": {}, + "outputs": [], + "source": [ + "## Educated suburban woman -> \n", + "# An embedding where:\n", + "# \"highest_education_Bachelor's degree\" == 1 or 'highest_education_Graduate degree or professional degree' == 1\n", + "# income_category >= 4 ( + more features that define 'suburban-ness')\n", + "# gender_Woman == 1\n", + "\n", + "demo_df = get_demographic_data(df)" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "17196eaf", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
has_drivers_licenseis_studentis_paidincome_categoryn_residence_membersn_residents_u18n_residents_with_licensen_motor_vehicleshas_medical_conditionft_jobmultiple_jobsn_working_residentshighest_education_Bachelor's degreehighest_education_Graduate degree or professional degreehighest_education_High school graduate or GEDhighest_education_Less than a high school graduatehighest_education_Prefer not to sayhighest_education_Some college or associates degreeprimary_job_description_Clerical or administrative supportprimary_job_description_Custodialprimary_job_description_Educationprimary_job_description_Food serviceprimary_job_description_Linecookprimary_job_description_Manufacturing, construction, maintenance, or farmingprimary_job_description_Medical/healthcareprimary_job_description_Non-profit program managerprimary_job_description_Otherprimary_job_description_Professional, managerial, or technicalprimary_job_description_Sales or serviceprimary_job_description_Self employedprimary_job_description_food servicegender_Mangender_Nonbinary/genderqueer/genderfluidgender_Prefer not to saygender_Womangender_Woman;Nonbinary/genderqueer/genderfluidage_16___20_years_oldage_21___25_years_oldage_26___30_years_oldage_31___35_years_oldage_36___40_years_oldage_41___45_years_oldage_46___50_years_oldage_51___55_years_oldage_56___60_years_oldage_61___65_years_oldage___65_years_oldav_transitav_no_tripav_p_microav_s_microav_ridehailav_unknownav_walkav_carav_s_cartarget
user_id
00db212b-c8d0-44cd-8392-41ab4065e6031004422100020.01.00.00.00.00.00.00.00.00.00.00.00.00.00.01.00.00.00.01.00.00.00.00.00.00.00.00.00.00.00.00.00.01.00.01011111111
0154d714-3928-4c34-b865-e5a417cd48af1013532400020.00.00.00.00.01.00.00.00.00.00.01.00.00.00.00.00.00.00.01.00.00.00.00.00.00.00.01.00.00.00.00.00.00.00.01011111119
03a395b4-d861-4757-bb84-32b4984559b01002302400030.00.00.00.00.01.00.00.00.01.00.00.00.00.00.00.00.00.00.01.00.00.00.00.00.00.00.00.00.01.00.00.00.00.00.01011111119
0a093cbd-b536-43af-b03d-293425e84c761011614100050.01.00.00.00.00.00.00.01.00.00.00.00.00.00.00.00.00.00.01.00.00.00.00.00.00.00.01.00.00.00.00.00.00.00.01011111119
0d0ae3a5-5641-4d13-8c52-a6040a203d241013422200120.00.01.00.00.00.00.00.00.00.00.00.00.00.00.00.01.00.00.01.00.00.00.00.00.00.00.00.00.00.00.00.01.00.00.01011111119
\n", + "
" + ], + "text/plain": [ + " has_drivers_license is_student \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 1 0 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 1 0 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 1 0 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 1 0 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 1 0 \n", + "\n", + " is_paid income_category \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 0 4 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 1 3 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 0 2 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 1 1 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 1 3 \n", + "\n", + " n_residence_members n_residents_u18 \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 4 2 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 5 3 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 3 0 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 6 1 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 4 2 \n", + "\n", + " n_residents_with_license \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 2 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 2 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 2 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 4 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 2 \n", + "\n", + " n_motor_vehicles has_medical_condition \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 1 0 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 4 0 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 4 0 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 1 0 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 2 0 \n", + "\n", + " ft_job multiple_jobs \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 0 0 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 0 0 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 0 0 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 0 0 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 0 1 \n", + "\n", + " n_working_residents \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 2 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 2 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 3 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 5 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 2 \n", + "\n", + " highest_education_Bachelor's degree \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 0.0 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 0.0 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 0.0 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 0.0 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 0.0 \n", + "\n", + " highest_education_Graduate degree or professional degree \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 1.0 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 0.0 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 0.0 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 1.0 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 0.0 \n", + "\n", + " highest_education_High school graduate or GED \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 0.0 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 0.0 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 0.0 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 0.0 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 1.0 \n", + "\n", + " highest_education_Less than a high school graduate \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 0.0 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 0.0 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 0.0 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 0.0 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 0.0 \n", + "\n", + " highest_education_Prefer not to say \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 0.0 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 0.0 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 0.0 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 0.0 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 0.0 \n", + "\n", + " highest_education_Some college or associates degree \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 0.0 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 1.0 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 1.0 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 0.0 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 0.0 \n", + "\n", + " primary_job_description_Clerical or administrative support \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 0.0 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 0.0 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 0.0 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 0.0 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 0.0 \n", + "\n", + " primary_job_description_Custodial \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 0.0 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 0.0 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 0.0 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 0.0 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 0.0 \n", + "\n", + " primary_job_description_Education \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 0.0 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 0.0 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 0.0 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 1.0 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 0.0 \n", + "\n", + " primary_job_description_Food service \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 0.0 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 0.0 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 1.0 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 0.0 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 0.0 \n", + "\n", + " primary_job_description_Linecook \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 0.0 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 0.0 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 0.0 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 0.0 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 0.0 \n", + "\n", + " primary_job_description_Manufacturing, construction, maintenance, or farming \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 0.0 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 1.0 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 0.0 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 0.0 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 0.0 \n", + "\n", + " primary_job_description_Medical/healthcare \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 0.0 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 0.0 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 0.0 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 0.0 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 0.0 \n", + "\n", + " primary_job_description_Non-profit program manager \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 0.0 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 0.0 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 0.0 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 0.0 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 0.0 \n", + "\n", + " primary_job_description_Other \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 0.0 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 0.0 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 0.0 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 0.0 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 0.0 \n", + "\n", + " primary_job_description_Professional, managerial, or technical \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 1.0 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 0.0 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 0.0 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 0.0 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 0.0 \n", + "\n", + " primary_job_description_Sales or service \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 0.0 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 0.0 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 0.0 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 0.0 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 1.0 \n", + "\n", + " primary_job_description_Self employed \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 0.0 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 0.0 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 0.0 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 0.0 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 0.0 \n", + "\n", + " primary_job_description_food service \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 0.0 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 0.0 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 0.0 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 0.0 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 0.0 \n", + "\n", + " gender_Man \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 1.0 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 1.0 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 1.0 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 1.0 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 1.0 \n", + "\n", + " gender_Nonbinary/genderqueer/genderfluid \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 0.0 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 0.0 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 0.0 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 0.0 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 0.0 \n", + "\n", + " gender_Prefer not to say gender_Woman \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 0.0 0.0 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 0.0 0.0 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 0.0 0.0 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 0.0 0.0 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 0.0 0.0 \n", + "\n", + " gender_Woman;Nonbinary/genderqueer/genderfluid \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 0.0 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 0.0 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 0.0 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 0.0 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 0.0 \n", + "\n", + " age_16___20_years_old \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 0.0 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 0.0 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 0.0 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 0.0 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 0.0 \n", + "\n", + " age_21___25_years_old \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 0.0 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 0.0 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 0.0 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 0.0 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 0.0 \n", + "\n", + " age_26___30_years_old \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 0.0 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 0.0 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 0.0 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 0.0 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 0.0 \n", + "\n", + " age_31___35_years_old \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 0.0 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 1.0 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 0.0 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 1.0 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 0.0 \n", + "\n", + " age_36___40_years_old \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 0.0 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 0.0 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 0.0 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 0.0 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 0.0 \n", + "\n", + " age_41___45_years_old \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 0.0 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 0.0 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 1.0 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 0.0 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 0.0 \n", + "\n", + " age_46___50_years_old \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 0.0 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 0.0 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 0.0 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 0.0 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 0.0 \n", + "\n", + " age_51___55_years_old \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 0.0 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 0.0 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 0.0 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 0.0 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 0.0 \n", + "\n", + " age_56___60_years_old \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 0.0 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 0.0 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 0.0 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 0.0 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 1.0 \n", + "\n", + " age_61___65_years_old \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 1.0 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 0.0 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 0.0 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 0.0 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 0.0 \n", + "\n", + " age___65_years_old av_transit \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 0.0 1 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 0.0 1 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 0.0 1 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 0.0 1 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 0.0 1 \n", + "\n", + " av_no_trip av_p_micro av_s_micro \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 0 1 1 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 0 1 1 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 0 1 1 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 0 1 1 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 0 1 1 \n", + "\n", + " av_ridehail av_unknown av_walk \\\n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 1 1 1 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 1 1 1 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 1 1 1 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 1 1 1 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 1 1 1 \n", + "\n", + " av_car av_s_car target \n", + "user_id \n", + "00db212b-c8d0-44cd-8392-41ab4065e603 1 1 1 \n", + "0154d714-3928-4c34-b865-e5a417cd48af 1 1 9 \n", + "03a395b4-d861-4757-bb84-32b4984559b0 1 1 9 \n", + "0a093cbd-b536-43af-b03d-293425e84c76 1 1 9 \n", + "0d0ae3a5-5641-4d13-8c52-a6040a203d24 1 1 9 " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "display(demo_df.head())" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "4c458c1a", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAj4AAAHFCAYAAADyj/PrAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAACa10lEQVR4nOzdd3wb9f348dfdaQ/LeyVOYrInhIQR9gx7lLI3ZZQSKJQfpdBFaNlQCqWFAm2BljK+hbIhhJUUCJCQELLIIHs53pYtWevufn+YOBGWHA/Jtuz38/HwA3yfk+4txz699Rnvj2KapokQQgghxACg9nYAQgghhBA9RRIfIYQQQgwYkvgIIYQQYsCQxEcIIYQQA4YkPkIIIYQYMCTxEUIIIcSAIYmPEEIIIQYMSXyEEEIIMWBI4iOEEEKIAUMSHyEy2Lx585g5cyb19fUdfoxpmrzwwgsceuihFBYW4nA4GDx4MMcddxx/+9vf4s5VFAVFUbjnnnvaPM/TTz+Noih8+eWXrcdmzpzZ+phEXxs2bOjqS42zYcMGFEXh6aef7vRjV6xYwcyZMzsVy4svvsj48eNxOp0oisLixYs7fd3u2vnz7srP8O2332bmzJkpj0mITCSJjxAZbN68edx+++2dSnxuvfVWzjvvPMaOHcvf/vY33nnnHe644w6Kiop47bXXEj7mnnvuoba2tsPXmDVrFp999lmbr5KSkg4/R7qsWLGC22+/vcMJRFVVFRdddBHDhw9vfV2jRo1Kb5Ap9vbbb3P77bf3dhhC9AmW3g5ACNFzmpubeeihh7j44ot54okn4touvfRSDMNo85hjjjmGOXPmcOedd/KHP/yhQ9eZMmUK+fn5KYm5t61evZpoNMqFF17I4YcfnpLnDAaDuFyulDyXEKJzpMdHiAw1c+ZMfv7znwNQXl7eOpw0Z86cpI8JBAKEw+GkPS+q2vaWMHr0aC6//HL+8pe/sHHjxpTEnsywYcM4+eSTeeWVV5g0aRIOh4O99tqLP/3pTx16/CeffMLRRx+N1+vF5XJx0EEH8dZbb7W2P/3005x11lkAHHnkka0/s2RDZpdeeimHHHIIAOeccw6KonDEEUe0tr/++utMmzYNl8uF1+vl2GOP5bPPPot7jp3Df4sWLeLMM88kJyeH4cOHt/s6Pv/8cw4++GAcDgelpaXceuutRKPRNue9+OKLTJ8+nZKSEpxOJ2PHjuWWW24hEAjEvYa//OUvAAmHHf/yl79w2GGHUVhYiNvtZuLEidx3330JrydEfyA9PkJkqCuuuILa2loeeeQR/vvf/7YmM+PGjUv6mPz8fEaMGMGjjz5KYWEhJ554IqNHj0ZRlHavNXPmTP71r3/xm9/8hn/+8597jE3XdWKxWNwxRVHQNG2Pj128eDE33HADM2fOpLi4mH//+99cf/31RCIRbrrppqSPmzt3LsceeyyTJk3i73//O3a7nUcffZRTTjmF559/nnPOOYeTTjqJu+66i1/+8pf85S9/Yd999wVImoj85je/Yf/992fGjBncddddHHnkkWRlZQHw3HPPccEFFzB9+nSef/55wuEw9913H0cccQQffPBBa8K00xlnnMG5557L1VdfHZeYfN+KFSs4+uijGTZsGE8//TQul4tHH32U5557rs25a9as4cQTT+SGG27A7XazcuVK7r33XubPn8+HH37Y+hoCgQAvvfRSXFK28/dl7dq1nH/++ZSXl2Oz2fj666+58847WblyJf/4xz+SxilExjKFEBnr/vvvNwFz/fr1HX7M/PnzzSFDhpiACZher9c8+eSTzX/+85+mYRhx5wLmjBkzTNM0zV/96lemqqrm119/bZqmaT711FMmYC5YsKD1/Ntuu631eb//NXz48D3GNnToUFNRFHPx4sVxx4899lgzKyvLDAQCpmma5vr1603AfOqpp1rPOfDAA83CwkKzsbGx9VgsFjMnTJhgDh48uPW1/ec//zEB86OPPurQz+ujjz4yAfM///lP6zFd183S0lJz4sSJpq7rrccbGxvNwsJC86CDDmrzM/ntb3/boeudc845ptPpNCsqKuJex5gxY9r9tzYMw4xGo+bcuXNNoPXfyTRNc8aMGWZHbve6rpvRaNT85z//aWqaZtbW1nYoZiEyiQx1CdEPGYZBLBZr/dJ1vbVtv/3249tvv2XWrFn88pe/ZNq0aXzwwQdcfPHFnHrqqZimmfA5b775ZnJzc/nFL36xx+u///77LFiwIO7r1Vdf7VDs48ePZ++99447dv755+P3+1m0aFHCxwQCAb744gvOPPNMPB5P63FN07jooovYsmULq1at6tD1O2LVqlVs27aNiy66KG540OPx8MMf/pDPP/+cYDAY95gf/vCHHXrujz76iKOPPpqioqLWY5qmcc4557Q5d926dZx//vkUFxejaRpWq7V1HtI333zToet99dVXnHrqqeTl5bU+x8UXX4yu66xevbpDzyFEJpGhLiH6od/97ndxq3iGDh0at4rJarVy3HHHcdxxxwFQU1PDmWeeyZtvvsk777zDiSee2OY5s7Ky+PWvf80NN9zARx991O7199577y5Pbi4uLk56rKamJuFj6urqME0z4dyl0tLSdh/bFTufK9n1DMOgrq4ubgJzR1e01dTUtPsz2KmpqYlDDz0Uh8PBHXfcwahRo3C5XGzevJkzzjiD5ubmPV5r06ZNHHrooYwePZqHH36YYcOG4XA4mD9/PjNmzOjQcwiRaSTxEaIfuuqqqzj55JNbv7fb7e2en5eXxw033MCcOXNYtmxZwsQH4Cc/+QkPP/wwv/jFL/jJT36S0ph3qqioSHosLy8v4WNycnJQVZXt27e3adu2bRtASleZ7Ywj2fVUVSUnJyfu+J7mUe3+3O39DHb68MMP2bZtG3PmzIlbbdaZ0gavvvoqgUCA//73vwwdOrT1eG/UKRKip8hQlxAZbGdC8/1P5qWlpUydOrX1a+LEiQBEo9GkPR87h0Z29pAkYrPZuOOOO1iwYAH/+c9/UvES2li+fDlff/113LHnnnsOr9fbOhn5+9xuNwcccAD//e9/434WhmHw7LPPMnjw4NbaO8l+Zp0xevRoBg0axHPPPRc3NBgIBHj55ZdbV3p1xZFHHskHH3zAjh07Wo/pus6LL74Yd97OROr7Se3jjz/e5jmTveZEz2GaJk8++WSXYhciE0iPjxAZbGdC8/DDD3PJJZdgtVoZPXo0Xq834fkNDQ0MGzaMs846i2OOOYaysjKampqYM2cODz/8MGPHjuWMM85o95rnnXceDzzwAO+8807ScxYuXIjP52tzfNy4ca2ropIpLS3l1FNPZebMmZSUlPDss8/y3nvvce+997abTNx9990ce+yxHHnkkdx0003YbDYeffRRli1bxvPPP9/6Jj9hwgQAnnjiCbxeLw6Hg/Ly8qS9SYmoqsp9993HBRdcwMknn8yPf/xjwuEw999/P/X19QkrXXfUr3/9a15//XWOOuoofvvb3+JyufjLX/7SZiXYQQcdRE5ODldffTW33XYbVquVf//7322SRtj1e3LvvfdywgknoGkakyZN4thjj8Vms3Heeedx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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "tsne_kwargs = {\n", + " 'perplexity': 6,\n", + " 'n_iter': 7500,\n", + " 'metric': 'cosine'\n", + "}\n", + "\n", + "## PLOT BY THE WAY IN WHICH PEOPLE USE THE SAME REPLACED MODE AND CHECK THE SIMILARITY.\n", + "\n", + "projections = generate_tsne_plots(demo_df, **tsne_kwargs)" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "c023cf66", + "metadata": {}, + "outputs": [], + "source": [ + "# No stratification, pure random.\n", + "demo_df.reset_index(drop=False, inplace=True)\n", + "train, test = train_test_split(demo_df, test_size=0.2, random_state=SEED)\n", + "\n", + "TRAIN_USERS = train.user_id.unique().tolist()\n", + "TEST_USERS = test.user_id.unique().tolist()" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "376a4391", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "81 21\n" + ] + } + ], + "source": [ + "print(train.shape[0], test.shape[0])" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "630d6c08", + "metadata": {}, + "outputs": [], + "source": [ + "# Ensuring that no user information is leaked across sets.\n", + "assert train.shape[0] + test.shape[0] == len(df.user_id.unique())" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "ef77c9c8", + "metadata": {}, + "outputs": [], + "source": [ + "def evaluate_using_similarity(test_df, train_df, metric=SimilarityMetric.COSINE, **metric_kwargs):\n", + " \n", + " '''\n", + " This method treats each user row as a 'fingerprint' (embedding vector). We assume that we\n", + " have no idea about the test set labels. To find which replaced mode is most likely for the test\n", + " users, we compute the cosine similarity of each test user against the users in the training set.\n", + " For the most similar user, we use their target as a proxy for the test user's replaced mode.\n", + " This operates on the following intuition: If User A and User B are similar, then their replaced\n", + " modes are also similar.\n", + " '''\n", + " \n", + " tr_targets = train_df.target.values\n", + " tr = train_df.drop(columns=['target', 'user_id'], inplace=False).reset_index(drop=True, inplace=False)\n", + " \n", + " te_targets = test_df.target.values\n", + " te = test_df.drop(columns=['target', 'user_id'], inplace=False).reset_index(drop=True, inplace=False)\n", + " \n", + " if metric == SimilarityMetric.COSINE:\n", + " # Use cosine similarity to determine which element in the train set this user is closest to.\n", + " # Offset the columns from the second entry to exclude the user_id column.\n", + " # Returns a (n_te, n_tr) matrix.\n", + " sim = cosine_similarity(te.values, tr.values)\n", + " \n", + " # Compute the argmax across the train set.\n", + " argmax = np.argmax(sim, axis=1)\n", + "\n", + " # Index into the training targets to retrieve predicted label.\n", + " y_test_pred = tr_targets[argmax]\n", + " \n", + " elif metric == SimilarityMetric.EUCLIDEAN:\n", + " \n", + " # Here, we choose the embedding with the smallest L2 distance.\n", + " distances = euclidean_distances(te.values, tr.values)\n", + " \n", + " # We choose argmin\n", + " argmin = np.argmin(distances, axis=1)\n", + " \n", + " # Index into the targets.\n", + " y_test_pred = tr_targets[argmin]\n", + " \n", + " elif metric == SimilarityMetric.KNN:\n", + " \n", + " # Build the KNN classifier. By default, let it be 3.\n", + " knn = KNeighborsClassifier(\n", + " n_neighbors=metric_kwargs.pop('n_neighbors', 3),\n", + " weights='distance',\n", + " metric=metric_kwargs.pop('knn_metric', 'cosine'),\n", + " n_jobs=os.cpu_count()\n", + " )\n", + " \n", + " # Fit the data to the KNN model\n", + " knn.fit(tr, tr_targets)\n", + " \n", + " y_test_pred = knn.predict(te)\n", + " \n", + " elif metric == SimilarityMetric.KMEANS:\n", + " \n", + " # Build the model.\n", + " kmeans = KMeans(\n", + " n_clusters=metric_kwargs.pop('n_clusters', 8),\n", + " max_iter=metric_kwargs.pop('max_iter', 300),\n", + " n_init='auto',\n", + " random_state=SEED\n", + " )\n", + " \n", + " # Fit the clustering model\n", + " kmeans.fit(tr)\n", + " \n", + " # Construct the auxiliary df and merge with the training set.\n", + " label_df = pd.DataFrame({'label': kmeans.labels_, 'target': tr_targets}, index=tr.index)\n", + " \n", + " # Now, perform an inference on the test set.\n", + " predicted_labels = kmeans.predict(te)\n", + " \n", + " y_test_pred = []\n", + " for prediction in predicted_labels:\n", + " most_likely = label_df.loc[label_df.label == prediction, 'target'].value_counts().idxmax()\n", + " y_test_pred.append(most_likely)\n", + " \n", + " else:\n", + " raise NotImplementedError(\"Unknown similarity metric\")\n", + " \n", + " \n", + " f1 = f1_score(y_true=te_targets, y_pred=y_test_pred, average='weighted')\n", + " print(f\"Test F1 score using {metric.name} = {f1}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "1a95ad5e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Test F1 score using COSINE = 0.42692939244663386\n", + "Test F1 score using EUCLIDEAN = 0.4126984126984127\n", + "Test F1 score using KNN = 0.4393241167434716\n", + "Test F1 score using KMEANS = 0.4733893557422969\n" + ] + } + ], + "source": [ + "for metric in [\n", + " SimilarityMetric.COSINE, SimilarityMetric.EUCLIDEAN, SimilarityMetric.KNN, SimilarityMetric.KMEANS\n", + "]:\n", + " evaluate_using_similarity(test, train, metric, n_clusters=3)" + ] + }, + { + "cell_type": "markdown", + "id": "16e435a6", + "metadata": {}, + "source": [ + "Not bad - using just a simple random split gives us the following results:\n", + "\n", + "$allCEO$:\n", + "\n", + "```\n", + "Test F1 score using COSINE = 0.42692939244663386\n", + "Test F1 score using EUCLIDEAN = 0.4126984126984127\n", + "Test F1 score using KNN = 0.4393241167434716\n", + "Test F1 score using KMEANS = 0.4733893557422969\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "81f0e842", + "metadata": {}, + "outputs": [], + "source": [ + "def custom_nll_scorer(clf, X, y):\n", + " \n", + " # [[yp1, yp2, yp3, ...], [yp1, yp3, ...]]\n", + " y_pred = clf.predict_proba(X)\n", + " \n", + " return -log_loss(y_true=y, y_pred=y_pred, labels=sorted(np.unique(y)))" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "a3a6af8f", + "metadata": {}, + "outputs": [], + "source": [ + "def estimate_using_model(train, test, **model_kwargs):\n", + " \n", + " cv = model_kwargs.pop('cv', None)\n", + " n_splits = model_kwargs.pop('n_splits', 5)\n", + " n_iter = model_kwargs.pop('n_iter', 500)\n", + " \n", + " if cv is None:\n", + " # Define the train-val splitter.\n", + " cv = KFold(n_splits=n_splits, shuffle=True, random_state=SEED)\n", + " \n", + " params = {\n", + " 'n_estimators': np.arange(100, 1001, 50),\n", + " 'max_depth': [i for i in range(5, 101, 5)],\n", + " 'ccp_alpha': np.linspace(0, 1, 10),\n", + " 'class_weight': ['balanced', 'balanced_subsample', None],\n", + " 'min_samples_split': np.arange(2, 25, 2),\n", + " 'min_samples_leaf': np.arange(1, 25)\n", + " }\n", + " \n", + " rf = RandomForestClassifier(random_state=SEED)\n", + " \n", + " # Search over hparams to minimize negative log likelihood. \n", + "# clf = RandomizedSearchCV(\n", + "# rf, params, n_iter=n_iter, scoring=custom_nll_scorer, \n", + "# n_jobs=os.cpu_count(), cv=cv, random_state=SEED,\n", + "# verbose=0\n", + "# )\n", + " \n", + " clf = RandomizedSearchCV(\n", + " rf, params, n_iter=n_iter, scoring='f1_weighted', \n", + " n_jobs=cpu_count(), cv=cv, random_state=SEED,\n", + " verbose=0\n", + " )\n", + " \n", + " X_tr = train.drop(columns=['user_id', 'target'])\n", + " y_tr = train.target.values.ravel()\n", + " \n", + " scorer = clf.fit(X_tr, y_tr)\n", + " \n", + " best_model = scorer.best_estimator_\n", + " \n", + " print(f\"Best val score = {scorer.best_score_}\")\n", + " \n", + " X_te = test.drop(columns=['user_id', 'target'])\n", + " \n", + " # Use the best model to compute F1 on the test set.\n", + " test_f1 = f1_score(y_true=test.target.values, y_pred=best_model.predict(X_te), average='weighted')\n", + " \n", + " print(f\"Test F1 = {test_f1}\")\n", + " \n", + " return best_model" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "2fab93ed", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Best val score = 0.653870145236778\n", + "Test F1 = 0.4733893557422969\n" + ] + } + ], + "source": [ + "model = estimate_using_model(train, test)" + ] + }, + { + "cell_type": "markdown", + "id": "2988c1b2", + "metadata": {}, + "source": [ + "Interesting! The model is slightly on par with K-Means!" + ] + }, + { + "cell_type": "markdown", + "id": "c6b77353", + "metadata": {}, + "source": [ + "## Experiment 2: Demographics with trip summaries" + ] + }, + { + "cell_type": "markdown", + "id": "bf7753d4", + "metadata": {}, + "source": [ + "Now that we've performed experiments with solely demographic data, let's expand the feature set by including \n", + "trip summary statistics. We would like this approach to do better than the aforementioned baselines." + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "1d46ab0f", + "metadata": {}, + "outputs": [], + "source": [ + "demo_plus_trips = get_demographic_data(\n", + " df, \n", + " trip_features=['mph', 'section_duration_argmax', 'section_distance_argmax', 'start_local_dt_hour', 'end_local_dt_hour']\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "11c1ea2c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
user_idmph_mean_bicyclingmph_mean_carmph_mean_transitmph_mean_unknownmph_mean_walkingmph_median_bicyclingmph_median_carmph_median_transitmph_median_unknownmph_median_walkingmph_iqr_bicyclingmph_iqr_carmph_iqr_transitmph_iqr_unknownmph_iqr_walkingsection_duration_argmax_mean_bicyclingsection_duration_argmax_mean_carsection_duration_argmax_mean_transitsection_duration_argmax_mean_unknownsection_duration_argmax_mean_walkingsection_duration_argmax_median_bicyclingsection_duration_argmax_median_carsection_duration_argmax_median_transitsection_duration_argmax_median_unknownsection_duration_argmax_median_walkingsection_duration_argmax_iqr_bicyclingsection_duration_argmax_iqr_carsection_duration_argmax_iqr_transitsection_duration_argmax_iqr_unknownsection_duration_argmax_iqr_walkingsection_distance_argmax_mean_bicyclingsection_distance_argmax_mean_carsection_distance_argmax_mean_transitsection_distance_argmax_mean_unknownsection_distance_argmax_mean_walkingsection_distance_argmax_median_bicyclingsection_distance_argmax_median_carsection_distance_argmax_median_transitsection_distance_argmax_median_unknownsection_distance_argmax_median_walkingsection_distance_argmax_iqr_bicyclingsection_distance_argmax_iqr_carsection_distance_argmax_iqr_transitsection_distance_argmax_iqr_unknownsection_distance_argmax_iqr_walkingstart_local_dt_hour_mode_bicyclingstart_local_dt_hour_mode_carstart_local_dt_hour_mode_transitstart_local_dt_hour_mode_unknownstart_local_dt_hour_mode_walkingend_local_dt_hour_mode_bicyclingend_local_dt_hour_mode_carend_local_dt_hour_mode_transitend_local_dt_hour_mode_unknownend_local_dt_hour_mode_walkingcoverage_bicyclingcoverage_carcoverage_transitcoverage_unknowncoverage_walkingdurationdistanceav_transitav_no_tripav_p_microav_s_microav_ridehailav_unknownav_walkav_carav_s_cartarget
000db212b-c8d0-44cd-8392-41ab4065e6036.23954514.27136716.8957978.7957201.8295106.09801812.31522313.7483537.3722352.0445051.7963567.66982818.2742834.1662291.09756512.14359316.17141228.51144325.60339916.0506339.90186112.89673719.89549118.38381711.8795277.36960710.65863842.3615779.81505012.6563331.3247244.65018510.7237964.9036720.4350720.9170192.2595734.3255832.0219000.3272310.9025122.98462315.8105422.2430130.31767215.016.016.015.015.015.018.020.019.015.01680.5774880.0652530.0212070.33605217.8275983.4867621011111111
10154d714-3928-4c34-b865-e5a417cd48af7.25882916.7074147.85892411.2521651.4617677.42292314.9763235.1738819.4572301.4421293.57763710.7309997.6388888.9724051.48870816.17563314.4433878.80715029.32569220.56848613.00784911.1019055.92815621.19006711.8724809.09568610.2453006.17266628.13307315.5183111.9068964.5954861.4031906.7307480.4037431.4790942.7538080.3892472.9760150.2257791.0938523.7971691.5192725.9046180.2291629.016.08.08.08.07.016.08.08.020.0320.8339540.0138370.0441720.10803615.8607674.1857541011111119
203a395b4-d861-4757-bb84-32b4984559b08.40045821.94990912.51254315.2609361.7622948.72543021.17186011.69777213.1901361.7671122.94259416.7477319.49420611.2532641.3903537.21557619.0635549.0424993.94860727.6379205.71037415.8116291.7985441.01983315.0089603.15509014.8431128.8978625.06857024.3540361.0079378.2275813.1005601.1437730.5886570.8162125.5555560.1657210.1699280.3761070.09717410.2074052.9612080.6629060.43375612.012.010.012.07.012.012.011.012.07.05070.6961510.0032760.0540540.24651924.7295015.8460631011111119
30a093cbd-b536-43af-b03d-293425e84c768.02320912.519217-1.0000008.7177451.5200886.85509711.158806-1.0000007.4118701.6359803.1327088.243087-1.0000004.9511391.51995716.41315229.697618-1.00000019.78011130.17552513.63953324.230089-1.00000016.38645520.4065509.50716724.436224-1.00000015.12991221.6783831.9685886.684421-1.0000003.4009540.6551201.7028043.907857-1.0000001.9204090.4621680.7483485.247948-1.0000001.5714510.69641014.017.00.013.015.015.08.00.013.012.0150.7475920.0000000.1117530.14065531.2773945.6680171011111119
40d0ae3a5-5641-4d13-8c52-a6040a203d246.53337618.3343158.42874312.1503431.7414966.71803114.3978099.60828710.7895071.9346052.11585717.7409534.8177948.1743461.42739413.91065518.1905699.02267442.45562417.42116311.76651613.7666477.50406929.04248310.4808116.58852515.7668177.20423643.09806711.1107031.5162246.8197151.2711318.2992120.4012491.3083213.0492421.25301911.1549820.2744140.77376210.5440070.77013610.8298070.31092010.011.011.015.014.014.010.013.020.014.0710.8409820.0064030.0522950.10032021.2827856.1902711011111119
\n", + "
" + ], + "text/plain": [ + " user_id mph_mean_bicycling mph_mean_car \\\n", + "0 00db212b-c8d0-44cd-8392-41ab4065e603 6.239545 14.271367 \n", + "1 0154d714-3928-4c34-b865-e5a417cd48af 7.258829 16.707414 \n", + "2 03a395b4-d861-4757-bb84-32b4984559b0 8.400458 21.949909 \n", + "3 0a093cbd-b536-43af-b03d-293425e84c76 8.023209 12.519217 \n", + "4 0d0ae3a5-5641-4d13-8c52-a6040a203d24 6.533376 18.334315 \n", + "\n", + " mph_mean_transit mph_mean_unknown mph_mean_walking mph_median_bicycling \\\n", + "0 16.895797 8.795720 1.829510 6.098018 \n", + "1 7.858924 11.252165 1.461767 7.422923 \n", + "2 12.512543 15.260936 1.762294 8.725430 \n", + "3 -1.000000 8.717745 1.520088 6.855097 \n", + "4 8.428743 12.150343 1.741496 6.718031 \n", + "\n", + " mph_median_car mph_median_transit mph_median_unknown mph_median_walking \\\n", + "0 12.315223 13.748353 7.372235 2.044505 \n", + "1 14.976323 5.173881 9.457230 1.442129 \n", + "2 21.171860 11.697772 13.190136 1.767112 \n", + "3 11.158806 -1.000000 7.411870 1.635980 \n", + "4 14.397809 9.608287 10.789507 1.934605 \n", + "\n", + " mph_iqr_bicycling mph_iqr_car mph_iqr_transit mph_iqr_unknown \\\n", + "0 1.796356 7.669828 18.274283 4.166229 \n", + "1 3.577637 10.730999 7.638888 8.972405 \n", + "2 2.942594 16.747731 9.494206 11.253264 \n", + "3 3.132708 8.243087 -1.000000 4.951139 \n", + "4 2.115857 17.740953 4.817794 8.174346 \n", + "\n", + " mph_iqr_walking section_duration_argmax_mean_bicycling \\\n", + "0 1.097565 12.143593 \n", + "1 1.488708 16.175633 \n", + "2 1.390353 7.215576 \n", + "3 1.519957 16.413152 \n", + "4 1.427394 13.910655 \n", + "\n", + " section_duration_argmax_mean_car section_duration_argmax_mean_transit \\\n", + "0 16.171412 28.511443 \n", + "1 14.443387 8.807150 \n", + "2 19.063554 9.042499 \n", + "3 29.697618 -1.000000 \n", + "4 18.190569 9.022674 \n", + "\n", + " section_duration_argmax_mean_unknown section_duration_argmax_mean_walking \\\n", + "0 25.603399 16.050633 \n", + "1 29.325692 20.568486 \n", + "2 3.948607 27.637920 \n", + "3 19.780111 30.175525 \n", + "4 42.455624 17.421163 \n", + "\n", + " section_duration_argmax_median_bicycling \\\n", + "0 9.901861 \n", + "1 13.007849 \n", + "2 5.710374 \n", + "3 13.639533 \n", + "4 11.766516 \n", + "\n", + " section_duration_argmax_median_car section_duration_argmax_median_transit \\\n", + "0 12.896737 19.895491 \n", + "1 11.101905 5.928156 \n", + "2 15.811629 1.798544 \n", + "3 24.230089 -1.000000 \n", + "4 13.766647 7.504069 \n", + "\n", + " section_duration_argmax_median_unknown \\\n", + "0 18.383817 \n", + "1 21.190067 \n", + "2 1.019833 \n", + "3 16.386455 \n", + "4 29.042483 \n", + "\n", + " section_duration_argmax_median_walking \\\n", + "0 11.879527 \n", + "1 11.872480 \n", + "2 15.008960 \n", + "3 20.406550 \n", + "4 10.480811 \n", + "\n", + " section_duration_argmax_iqr_bicycling section_duration_argmax_iqr_car \\\n", + "0 7.369607 10.658638 \n", + "1 9.095686 10.245300 \n", + "2 3.155090 14.843112 \n", + "3 9.507167 24.436224 \n", + "4 6.588525 15.766817 \n", + "\n", + " section_duration_argmax_iqr_transit section_duration_argmax_iqr_unknown \\\n", + "0 42.361577 9.815050 \n", + "1 6.172666 28.133073 \n", + "2 8.897862 5.068570 \n", + "3 -1.000000 15.129912 \n", + "4 7.204236 43.098067 \n", + "\n", + " section_duration_argmax_iqr_walking \\\n", + "0 12.656333 \n", + "1 15.518311 \n", + "2 24.354036 \n", + "3 21.678383 \n", + "4 11.110703 \n", + "\n", + " section_distance_argmax_mean_bicycling section_distance_argmax_mean_car \\\n", + "0 1.324724 4.650185 \n", + "1 1.906896 4.595486 \n", + "2 1.007937 8.227581 \n", + "3 1.968588 6.684421 \n", + "4 1.516224 6.819715 \n", + "\n", + " section_distance_argmax_mean_transit section_distance_argmax_mean_unknown \\\n", + "0 10.723796 4.903672 \n", + "1 1.403190 6.730748 \n", + "2 3.100560 1.143773 \n", + "3 -1.000000 3.400954 \n", + "4 1.271131 8.299212 \n", + "\n", + " section_distance_argmax_mean_walking \\\n", + "0 0.435072 \n", + "1 0.403743 \n", + "2 0.588657 \n", + "3 0.655120 \n", + "4 0.401249 \n", + "\n", + " section_distance_argmax_median_bicycling \\\n", + "0 0.917019 \n", + "1 1.479094 \n", + "2 0.816212 \n", + "3 1.702804 \n", + "4 1.308321 \n", + "\n", + " section_distance_argmax_median_car section_distance_argmax_median_transit \\\n", + "0 2.259573 4.325583 \n", + "1 2.753808 0.389247 \n", + "2 5.555556 0.165721 \n", + "3 3.907857 -1.000000 \n", + "4 3.049242 1.253019 \n", + "\n", + " section_distance_argmax_median_unknown \\\n", + "0 2.021900 \n", + "1 2.976015 \n", + "2 0.169928 \n", + "3 1.920409 \n", + "4 11.154982 \n", + "\n", + " section_distance_argmax_median_walking \\\n", + "0 0.327231 \n", + "1 0.225779 \n", + "2 0.376107 \n", + "3 0.462168 \n", + "4 0.274414 \n", + "\n", + " section_distance_argmax_iqr_bicycling section_distance_argmax_iqr_car \\\n", + "0 0.902512 2.984623 \n", + "1 1.093852 3.797169 \n", + "2 0.097174 10.207405 \n", + "3 0.748348 5.247948 \n", + "4 0.773762 10.544007 \n", + "\n", + " section_distance_argmax_iqr_transit section_distance_argmax_iqr_unknown \\\n", + "0 15.810542 2.243013 \n", + "1 1.519272 5.904618 \n", + "2 2.961208 0.662906 \n", + "3 -1.000000 1.571451 \n", + "4 0.770136 10.829807 \n", + "\n", + " section_distance_argmax_iqr_walking start_local_dt_hour_mode_bicycling \\\n", + "0 0.317672 15.0 \n", + "1 0.229162 9.0 \n", + "2 0.433756 12.0 \n", + "3 0.696410 14.0 \n", + "4 0.310920 10.0 \n", + "\n", + " start_local_dt_hour_mode_car start_local_dt_hour_mode_transit \\\n", + "0 16.0 16.0 \n", + "1 16.0 8.0 \n", + "2 12.0 10.0 \n", + "3 17.0 0.0 \n", + "4 11.0 11.0 \n", + "\n", + " start_local_dt_hour_mode_unknown start_local_dt_hour_mode_walking \\\n", + "0 15.0 15.0 \n", + "1 8.0 8.0 \n", + "2 12.0 7.0 \n", + "3 13.0 15.0 \n", + "4 15.0 14.0 \n", + "\n", + " end_local_dt_hour_mode_bicycling end_local_dt_hour_mode_car \\\n", + "0 15.0 18.0 \n", + "1 7.0 16.0 \n", + "2 12.0 12.0 \n", + "3 15.0 8.0 \n", + "4 14.0 10.0 \n", + "\n", + " end_local_dt_hour_mode_transit end_local_dt_hour_mode_unknown \\\n", + "0 20.0 19.0 \n", + "1 8.0 8.0 \n", + "2 11.0 12.0 \n", + "3 0.0 13.0 \n", + "4 13.0 20.0 \n", + "\n", + " end_local_dt_hour_mode_walking coverage_bicycling coverage_car \\\n", + "0 15.0 168 0.577488 \n", + "1 20.0 32 0.833954 \n", + "2 7.0 507 0.696151 \n", + "3 12.0 15 0.747592 \n", + "4 14.0 71 0.840982 \n", + "\n", + " coverage_transit coverage_unknown coverage_walking duration distance \\\n", + "0 0.065253 0.021207 0.336052 17.827598 3.486762 \n", + "1 0.013837 0.044172 0.108036 15.860767 4.185754 \n", + "2 0.003276 0.054054 0.246519 24.729501 5.846063 \n", + "3 0.000000 0.111753 0.140655 31.277394 5.668017 \n", + "4 0.006403 0.052295 0.100320 21.282785 6.190271 \n", + "\n", + " av_transit av_no_trip av_p_micro av_s_micro av_ridehail av_unknown \\\n", + "0 1 0 1 1 1 1 \n", + "1 1 0 1 1 1 1 \n", + "2 1 0 1 1 1 1 \n", + "3 1 0 1 1 1 1 \n", + "4 1 0 1 1 1 1 \n", + "\n", + " av_walk av_car av_s_car target \n", + "0 1 1 1 1 \n", + "1 1 1 1 9 \n", + "2 1 1 1 9 \n", + "3 1 1 1 9 \n", + "4 1 1 1 9 " + ] + }, + "execution_count": 42, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "demo_plus_trips.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "6159c90a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "81 21\n" + ] + } + ], + "source": [ + "train = demo_plus_trips.loc[demo_plus_trips.user_id.isin(TRAIN_USERS), :]\n", + "test = demo_plus_trips.loc[demo_plus_trips.user_id.isin(TEST_USERS), :]\n", + "\n", + "print(train.shape[0], test.shape[0])" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "06e85bdd", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Test F1 score using COSINE = 0.32098765432098775\n", + "Test F1 score using EUCLIDEAN = 0.36684303350970027\n", + "Test F1 score using KNN = 0.41269841269841273\n", + "Test F1 score using KMEANS = 0.4877344877344878\n" + ] + } + ], + "source": [ + "for metric in [\n", + " SimilarityMetric.COSINE, SimilarityMetric.EUCLIDEAN, SimilarityMetric.KNN, SimilarityMetric.KMEANS\n", + "]:\n", + " evaluate_using_similarity(test, train, metric, n_clusters=4)" + ] + }, + { + "cell_type": "markdown", + "id": "ba795489", + "metadata": {}, + "source": [ + "Great! Some improvement here and there.\n", + "\n", + "$allCEO$\n", + "```\n", + "Test F1 score using COSINE = 0.32098765432098775\n", + "Test F1 score using EUCLIDEAN = 0.36684303350970027\n", + "Test F1 score using KNN = 0.41269841269841273\n", + "Test F1 score using KMEANS = 0.4877344877344878\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "9acd4b0b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Best val score = 0.6326031937974128\n", + "Test F1 = 0.004329004329004328\n" + ] + }, + { + "data": { + "text/html": [ + "
RandomForestClassifier(ccp_alpha=0.2222222222222222,\n",
+       "                       class_weight='balanced_subsample', max_depth=25,\n",
+       "                       min_samples_leaf=5, min_samples_split=14,\n",
+       "                       n_estimators=700, random_state=13210)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" + ], + "text/plain": [ + "RandomForestClassifier(ccp_alpha=0.2222222222222222,\n", + " class_weight='balanced_subsample', max_depth=25,\n", + " min_samples_leaf=5, min_samples_split=14,\n", + " n_estimators=700, random_state=13210)" + ] + }, + "execution_count": 45, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Now, we try with the model\n", + "estimate_using_model(train, test)" + ] + }, + { + "cell_type": "markdown", + "id": "cd94c548", + "metadata": {}, + "source": [ + "Great! Compared to the previous model, we see definite improvements! I'm sure we can squeeze some more juice out of the models using fancy optimization, but as a baseline, these are good enough.\n", + "\n", + "\n", + "So, to recap:\n", + "$F1_{cosine} = 0.37$, $F1_{euclidean} = 0.33$, $F1_{knn} = 0.3$, $F1_{kmeans} = 0.36$, $F1_{RF} = 0.4215$" + ] + }, + { + "cell_type": "markdown", + "id": "8a8f6491", + "metadata": {}, + "source": [ + "### Different groupings." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9ce90367", + "metadata": {}, + "outputs": [], + "source": [ + "# trip_features = ['mph', 'section_duration_argmax', 'section_distance_argmax', 'start:hour', 'end:hour']\n", + "\n", + "# for group_mode in ['section_mode_argmax', 'section_distance_argmax', 'section_duration_argmax', 'duration', 'distance']:\n", + " \n", + "# if group_mode in trip_features:\n", + "# _ = trip_features.pop(trip_features.index(group_mode))\n", + " \n", + "# exp_df = get_demographic_data(\n", + "# df, \n", + "# trip_grouping=group_mode,\n", + "# trip_features=trip_features,\n", + "# use_qcut=True\n", + "# )\n", + " \n", + "# train, test = train_test_split(exp_df, test_size=0.2, random_state=SEED)\n", + " \n", + "# for sim in [\n", + "# SimilarityMetric.COSINE, SimilarityMetric.EUCLIDEAN, SimilarityMetric.KNN, SimilarityMetric.KMEANS\n", + "# ]:\n", + "# evaluate_using_similarity(test, train, sim, n_clusters=3)\n", + " \n", + "# # estimate_using_model(train, test, n_iter=200)\n", + " \n", + "# print(50*'=')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6d53f945", + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "_ = generate_tsne_plots(demo_plus_trips, perplexity=6, n_iter=7500)" + ] + }, + { + "cell_type": "markdown", + "id": "c339fcc6", + "metadata": {}, + "source": [ + "# Multi-level modeling" + ] + }, + { + "cell_type": "markdown", + "id": "213676ec", + "metadata": {}, + "source": [ + "In this approach, we want to piece together the similarity search and modeling processes. Here's a rough sketch of how it should be implemented:\n", + "\n", + "1. For every user in the training set, build a model using their entire trip history.\n", + "2. Consolidate these user-level models in data structure, preferably a dictionary.\n", + "3. Now, when we want to perform inference on a new user with no prior trips, we use the similarity search to get the user ID in the training set who is the most similar to the user in question.\n", + "4. We retrieve the model for this corresponding user and perform an inference. The hypothesis is that since the two users are similar, their trip substitution patterns are also similar." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c48ee430", + "metadata": {}, + "outputs": [], + "source": [ + "def drop_columns(df: pd.DataFrame):\n", + " to_drop = [\n", + " 'source', 'end_ts', 'end_fmt_time', 'end_loc', 'raw_trip', 'start_ts', \n", + " 'start_fmt_time', 'start_loc', 'duration', 'distance', 'start_place', \n", + " 'end_place', 'cleaned_trip', 'inferred_labels', 'inferred_trip', 'expectation',\n", + " 'confidence_threshold', 'expected_trip', 'user_input', 'start:year', 'start:month', \n", + " 'start:day', 'start_local_dt_minute', 'start_local_dt_second', \n", + " 'start_local_dt_weekday', 'start_local_dt_timezone', 'end:year', 'end:month', 'end:day', \n", + " 'end_local_dt_minute', 'end_local_dt_second', 'end_local_dt_weekday', \n", + " 'end_local_dt_timezone', '_id', 'metadata_write_ts', 'additions', \n", + " 'mode_confirm', 'purpose_confirm', 'Mode_confirm', 'Trip_purpose', \n", + " 'original_user_id', 'program', 'opcode', 'Timestamp', 'birth_year', \n", + " 'available_modes', 'section_coordinates_argmax', 'section_mode_argmax'\n", + " ]\n", + " \n", + " # Drop section_mode_argmax and available_modes.\n", + " return df.drop(\n", + " columns=to_drop, \n", + " inplace=False\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ca9e6e6a", + "metadata": {}, + "outputs": [], + "source": [ + "def construct_model_dictionary(train: pd.DataFrame):\n", + " \n", + " def train_on_user(user_id: str):\n", + " '''\n", + " Given the training set and the user ID to query, filter the dataset and\n", + " retain only the relevant trips. Then, create folds and optimize a model for this user.\n", + " Return the trained model instance.\n", + " '''\n", + " \n", + " user_data = train.loc[train.user_id == user_id, :].reset_index(drop=True)\n", + " \n", + " # Split user trips into train-test folds.\n", + " u_train, u_test = train_test_split(user_data, test_size=0.2, shuffle=True, random_state=SEED)\n", + " \n", + " user_model = estimate_using_model(\n", + " u_train, u_test, \n", + " n_iter=100\n", + " )\n", + " \n", + " return user_model\n", + " \n", + " for user in train.user_id.unique():\n", + " MODEL_DICT[user]['warm_start'] = train_on_user(user)\n", + " print(50*'=')\n", + " \n", + " print(\"\\nDone!\")" + ] + }, + { + "cell_type": "markdown", + "id": "2a035c16", + "metadata": {}, + "source": [ + "## Warm start:\n", + "\n", + "If the queried user has prior trips, we know that we we can harness the additional information. So if we encounter such a user, we will first find the most similar user (using only demographics). Once the most similar user is found, we query the trip model for the user and run inference through it.\n", + "\n", + "## Cold start:\n", + "\n", + "If the queried user has no prior trips, we will use the demo-only model. We first perform a similarity search and then run user inference through the demo-only model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "082c4e39", + "metadata": {}, + "outputs": [], + "source": [ + "class MultiLevelModel:\n", + " def __init__(self, model_dict: Dict, train: pd.DataFrame, test: pd.DataFrame, **model_kwargs):\n", + " \n", + " self._demographics = [\n", + " 'primary_job_commute_time', 'income_category', 'n_residence_members', 'n_residents_u18', \n", + " 'n_residents_with_license', 'n_motor_vehicles', 'available_modes', 'age', 'gender_Man', \n", + " 'gender_Man;Nonbinary/genderqueer/genderfluid', 'gender_Nonbinary/genderqueer/genderfluid', \n", + " 'gender_Prefer not to say', 'gender_Woman', 'gender_Woman;Nonbinary/genderqueer/genderfluid', \n", + " 'has_drivers_license_No', 'has_drivers_license_Prefer not to say', 'has_drivers_license_Yes', \n", + " 'has_multiple_jobs_No', 'has_multiple_jobs_Prefer not to say', 'has_multiple_jobs_Yes', \n", + " \"highest_education_Bachelor's degree\", 'highest_education_Graduate degree or professional degree', \n", + " 'highest_education_High school graduate or GED', 'highest_education_Less than a high school graduate', \n", + " 'highest_education_Prefer not to say', 'highest_education_Some college or associates degree', \n", + " 'primary_job_type_Full-time', 'primary_job_type_Part-time', 'primary_job_type_Prefer not to say', \n", + " 'primary_job_description_Clerical or administrative support', 'primary_job_description_Custodial', \n", + " 'primary_job_description_Education', 'primary_job_description_Food service', \n", + " 'primary_job_description_Manufacturing, construction, maintenance, or farming', \n", + " 'primary_job_description_Medical/healthcare', 'primary_job_description_Other', \n", + " 'primary_job_description_Professional, managerial, or technical', \n", + " 'primary_job_description_Sales or service', 'primary_job_commute_mode_Active transport', \n", + " 'primary_job_commute_mode_Car transport', 'primary_job_commute_mode_Hybrid', \n", + " 'primary_job_commute_mode_Public transport', 'primary_job_commute_mode_Unknown', \n", + " 'primary_job_commute_mode_WFH', 'is_overnight_trip', 'n_working_residents'\n", + " ]\n", + " \n", + " assert all([c in test.columns for c in self._demographics]), \"[test] Demographic features are missing!\"\n", + " assert all([c in train.columns for c in self._demographics]), \"[train] Demographic features are missing!\"\n", + " \n", + " self._mdict = model_dict\n", + " self._train = train\n", + " self._test = test\n", + " self.metric = model_kwargs.pop('metric', SimilarityMetric.COSINE)\n", + " \n", + " \n", + " def _phase1(self):\n", + " \n", + " tr = self._train.copy()\n", + " te = self._test.copy()\n", + " \n", + " if tr.columns.isin(['user_id', 'target']).sum() == 2:\n", + " tr = tr.drop(columns=['user_id', 'target']).reset_index(drop=True)\n", + " \n", + " if te.columns.isin(['user_id', 'target']).sum() == 2:\n", + " te = te.drop(columns=['user_id', 'target']).reset_index(drop=True)\n", + "\n", + " te_users = self._test.user_id.tolist()\n", + "\n", + " if self.metric == SimilarityMetric.COSINE:\n", + "\n", + " sim = cosine_similarity(te.values, tr.values)\n", + "\n", + " # Compute the argmax across the train set.\n", + " argmax = np.argmax(sim, axis=1)\n", + "\n", + " # Retrieve the user_id at these indices.\n", + " train_users = self._train.loc[argmax, 'user_id']\n", + "\n", + " elif self.metric == SimilarityMetric.EUCLIDEAN:\n", + "\n", + " sim = euclidean_distances(te.values, tr.values)\n", + "\n", + " # Compute the argmin here!\n", + " argmin = np.argmin(sim, axis=1)\n", + "\n", + " # Retrieve the train user_ids.\n", + " train_users = self._train.loc[argmin, 'user_id']\n", + "\n", + " return pd.DataFrame({'test_user_id': te_users, 'train_user_id': train_users})\n", + " \n", + " \n", + " def _phase2(self, sim_df: pd.DataFrame, cold_start: bool):\n", + " \n", + " prediction_df = list()\n", + " \n", + " # Now, we use the sim_df to run inference based on whether \n", + " for ix, row in sim_df.iterrows():\n", + " train_user = row['train_user_id']\n", + " \n", + " # Retrieve the appropriate model.\n", + " user_models = self._mdict.get(train_user, None)\n", + " \n", + " start_type = 'cold_start' if cold_start else 'warm_start'\n", + " \n", + " # which specific model?\n", + " sp_model = user_models.get(start_type, None)\n", + " \n", + " # Now get the test user data.\n", + " test_user = row['test_user_id']\n", + " \n", + " if cold_start:\n", + " test_data = self._test.loc[self._test.user_id == test_user, self._demographics]\n", + " test_data = test_data.iloc[0, :]\n", + " else:\n", + " test_data = self._test.loc[self._test.user_id == test_user, :]\n", + " \n", + " predictions = sp_model.predict(test_data)\n", + " \n", + " print(f\"test: [{test_user}], predictions: {predictions}\")\n", + " \n", + " \n", + " def execute_pipeline(self, cold_start: bool = False):\n", + " # For each test user, get the most similar train user.\n", + " sim_df = self._phase1()\n", + " \n", + " predictions = self._phase2(sim_df, cold_start)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eb63632d", + "metadata": {}, + "outputs": [], + "source": [ + "# FULL DATA.\n", + "train = df.loc[df.user_id.isin(TRAIN_USERS), :]\n", + "test = df.loc[df.user_id.isin(TEST_USERS), :]\n", + "\n", + "train_counts = train.user_id.value_counts()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f2528eaa", + "metadata": {}, + "outputs": [], + "source": [ + "## We only want to train on users who have a good number of trips.\n", + "good_users = train_counts[train_counts >= 100].index\n", + "\n", + "bad_users = train_counts[train_counts < 100].index\n", + "\n", + "print(f\"Number of users filtered out of training: {len(bad_users)}\")\n", + "\n", + "filtered_train = train.loc[train.user_id.isin(good_users), :]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bae55b21", + "metadata": {}, + "outputs": [], + "source": [ + "# Full data.\n", + "\n", + "train_df = drop_columns(filtered_train)\n", + "test_df = drop_columns(test)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "88d0e2d2", + "metadata": {}, + "outputs": [], + "source": [ + "print(train_df.shape, test_df.shape)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "37febd6d", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "model_dict = construct_model_dictionary(train_df)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b1249925", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "emission", + "language": "python", + "name": "emission" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.16" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/rm_src/04_FeatureClustering.ipynb b/rm_src/04_FeatureClustering.ipynb new file mode 100644 index 0000000..e61668c --- /dev/null +++ b/rm_src/04_FeatureClustering.ipynb @@ -0,0 +1,1108 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "789df947", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import numpy as np\n", + "import random\n", + "import os\n", + "import itertools\n", + "import pickle\n", + "import ast\n", + "import matplotlib.pyplot as plt\n", + "import matplotlib.colors as mcolors\n", + "import seaborn as sns\n", + "\n", + "from sklearn.linear_model import LinearRegression\n", + "from sklearn.metrics.pairwise import cosine_similarity, euclidean_distances\n", + "from sklearn.metrics import davies_bouldin_score, calinski_harabasz_score, silhouette_score\n", + "from sklearn.preprocessing import MinMaxScaler, StandardScaler\n", + "from typing import List, Dict, Union\n", + "from pandas.api.types import is_numeric_dtype\n", + "from sklearn.cluster import DBSCAN, KMeans\n", + "from collections import Counter\n", + "\n", + "pd.set_option('display.max_columns', None)\n", + "\n", + "%matplotlib inline\n", + "\n", + "SEED = 13210\n", + "\n", + "np.random.seed(SEED)\n", + "random.seed(SEED)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "aea4dda7", + "metadata": {}, + "outputs": [], + "source": [ + "DATA_SOURCES = [\n", + " ('../data/filtered_data/preprocessed_data_Stage_database.csv', 'allceo'),\n", + " ('../data/filtered_data/preprocessed_data_openpath_prod_durham.csv', 'durham'),\n", + " ('../data/filtered_data/preprocessed_data_openpath_prod_ride2own.csv', 'ride2own'),\n", + " ('../data/filtered_data/preprocessed_data_openpath_prod_mm_masscec.csv', 'masscec'),\n", + " ('../data/filtered_data/preprocessed_data_openpath_prod_uprm_nicr.csv', 'nicr')\n", + "]\n", + "\n", + "# Switch between 0-4\n", + "DB_NUMBER = 0" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "33ef3275", + "metadata": {}, + "outputs": [], + "source": [ + "# Change this name to something unique\n", + "CURRENT_DB = DATA_SOURCES[DB_NUMBER][1]\n", + "PATH = DATA_SOURCES[DB_NUMBER][0]\n", + "\n", + "df = pd.read_csv(PATH)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d0d884a3", + "metadata": {}, + "outputs": [], + "source": [ + "df.target.value_counts()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b2281bdc", + "metadata": {}, + "outputs": [], + "source": [ + "df.rename(\n", + " columns={'end_local_dt_hour': 'end:hour', 'start_local_dt_hour': 'start:hour', 'replaced_mode': 'target'}, \n", + " inplace=True\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9c22d6ac", + "metadata": {}, + "outputs": [], + "source": [ + "TARGETS = ['p_micro', 'no_trip', 's_car', 'transit', 'car', 's_micro', 'ridehail', 'walk', 'unknown']\n", + "MAP = {ix+1: t for (ix, t) in enumerate(TARGETS)}\n", + "TARGET_MAP = {v:k for k, v in MAP.items()}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "063f6124", + "metadata": {}, + "outputs": [], + "source": [ + "df.replace({'target': TARGET_MAP}, inplace=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cef8d45b", + "metadata": {}, + "outputs": [], + "source": [ + "# % of trips per mode.\n", + "trip_percents = df.groupby(['user_id'])['section_mode_argmax'].apply(lambda x: x.value_counts(normalize=True)).unstack(level=-1)\n", + "trip_percents.fillna(0., inplace=True)\n", + "\n", + "trip_percents.columns = ['coverage_'+x for x in trip_percents.columns]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "68c6af2d", + "metadata": {}, + "outputs": [], + "source": [ + "n_trips = pd.DataFrame(df.groupby('user_id').size(), columns=['n_trips'])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eff378a7", + "metadata": {}, + "outputs": [], + "source": [ + "most_common_start = df.groupby('user_id')['start:hour'].apply(lambda x: x.value_counts().idxmax())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cffbd401", + "metadata": {}, + "outputs": [], + "source": [ + "most_common_end = df.groupby('user_id')['end:hour'].apply(lambda x: x.value_counts().idxmax())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f1eb1633", + "metadata": {}, + "outputs": [], + "source": [ + "# % of distance in each primary sensed mode.\n", + "total_distance = df.groupby(['user_id', 'section_mode_argmax'])['section_distance_argmax'].sum().unstack(level=-1)\n", + "total_distance = total_distance.div(total_distance.sum(axis=1), axis=0)\n", + "total_distance.fillna(0., inplace=True)\n", + "total_distance.columns = ['pct_distance_' + x for x in total_distance.columns]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d9cc0a0f", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "figure1_df = trip_percents.merge(right=total_distance, left_index=True, right_index=True).merge(\n", + " right=n_trips, left_index=True, right_index=True\n", + ").merge(\n", + " right=most_common_start, left_index=True, right_index=True\n", + ").merge(right=most_common_end, left_index=True, right_index=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "750fbd0c", + "metadata": {}, + "outputs": [], + "source": [ + "# Normalize the last three columns.\n", + "\n", + "def min_max_normalize(col: pd.Series):\n", + " _max, _min = col.max(), col.min()\n", + " return pd.Series((col - _min)/(_max - _min))\n", + "\n", + "figure1_df['n_trips'] = min_max_normalize(figure1_df['n_trips'])\n", + "figure1_df['start:hour'] = np.sin(figure1_df['start:hour'].values)\n", + "figure1_df['end:hour'] = np.sin(figure1_df['end:hour'].values)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1c3d1849", + "metadata": {}, + "outputs": [], + "source": [ + "figure1_df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "598d82bc", + "metadata": {}, + "outputs": [], + "source": [ + "epsilons = np.linspace(1e-3, 1., 1000)\n", + "\n", + "best_eps = -np.inf\n", + "best_score = -np.inf\n", + "\n", + "for eps in epsilons:\n", + " model = DBSCAN(eps=eps).fit(figure1_df)\n", + " \n", + " if len(np.unique(model.labels_)) < 2:\n", + " continue\n", + " \n", + " score = silhouette_score(figure1_df, model.labels_)\n", + " if score > best_score:\n", + " best_eps = eps\n", + " best_score = score\n", + "\n", + "print(best_eps)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bc89a42d", + "metadata": {}, + "outputs": [], + "source": [ + "'''\n", + "AlLCEO: eps=0.542\n", + "durham: eps=0.661\n", + "masscec: eps=0.64\n", + "'''\n", + "\n", + "clustering = DBSCAN(eps=0.8).fit(figure1_df)\n", + "\n", + "print(Counter(clustering.labels_))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "05c9a7c4", + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "# After clustering, we would like to see what the replaced mode argmax distribution in each cluster is.\n", + "\n", + "labels = clustering.labels_\n", + "\n", + "for cix in np.unique(labels):\n", + " cluster_users = figure1_df.iloc[labels == cix,:].index\n", + " \n", + " print(f\"{len(cluster_users)} users in cluster {cix}\")\n", + " \n", + " # Now, for each user, look at the actual data and determine the replaced mode argmax distribution.\n", + " sub_df = df.loc[df.user_id.isin(cluster_users), :].reset_index(drop=True)\n", + " \n", + " sub_df['target'] = sub_df['target'].apply(lambda x: MAP[x])\n", + " \n", + " rm_argmax = sub_df.groupby('user_id')['target'].apply(lambda x: x.value_counts().idxmax())\n", + " fig, ax = plt.subplots()\n", + " rm_argmax.hist(ax=ax)\n", + " ax.set_title(f\"Replaced mode argmax distribution for users in cluster {cix}\")\n", + " ax.set_xlabel(\"Target\")\n", + " \n", + " plt.savefig(f'../outputs/{CURRENT_DB}__FIG1_cluster_{cix}_target_dist.png', dpi=300)\n", + " \n", + " plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f2e8e117", + "metadata": {}, + "outputs": [], + "source": [ + "user_target_pct = pd.DataFrame()\n", + "\n", + "# For every user, compute the replaced mode distribution.\n", + "for user_id, user_data in df.groupby('user_id'):\n", + " \n", + " target_distribution = user_data['target'].value_counts(normalize=True)\n", + " target_distribution.rename(index=MAP, inplace=True)\n", + " user_target_pct = pd.concat([user_target_pct, target_distribution.to_frame(user_id).T])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "99369dba", + "metadata": {}, + "outputs": [], + "source": [ + "user_target_pct.columns = ['pct_trips_' + str(x) for x in user_target_pct.columns]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6cca3671", + "metadata": {}, + "outputs": [], + "source": [ + "target_distance = pd.DataFrame()\n", + "\n", + "# For every user, compute the replaced mode distribution.\n", + "for user_id, user_data in df.groupby('user_id'):\n", + " \n", + " # total_distance = user_data['distance'].sum()\n", + " distance_per_target = user_data.groupby('target')['section_distance_argmax'].sum()\n", + " distance_per_target.rename(index=MAP, inplace=True)\n", + " row = distance_per_target.to_frame(user_id).T\n", + " target_distance = pd.concat([target_distance, row])\n", + " \n", + "target_distance.columns = ['distance_' + str(x) for x in target_distance.columns]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18093734", + "metadata": {}, + "outputs": [], + "source": [ + "target_duration = df.groupby(['user_id', 'target'])['section_duration_argmax'].sum().unstack()\n", + "target_duration.rename(columns=MAP, inplace=True)\n", + "target_duration.fillna(0., inplace=True)\n", + "target_duration.columns = ['duration_' + str(x) for x in target_duration.columns]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8001a140", + "metadata": {}, + "outputs": [], + "source": [ + "target_df = user_target_pct.merge(right=target_distance, left_index=True, right_index=True).merge(\n", + " right=target_duration, left_index=True, right_index=True\n", + ")\n", + "\n", + "target_df.fillna(0., inplace=True)\n", + "\n", + "target_df = pd.DataFrame(\n", + " MinMaxScaler().fit_transform(target_df),\n", + " columns=target_df.columns,\n", + " index=target_df.index\n", + ")\n", + "\n", + "display(target_df)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "31fecc00", + "metadata": {}, + "outputs": [], + "source": [ + "epsilons = np.linspace(5e-3, 1., 1500)\n", + "best_score = -np.inf\n", + "best_eps = None\n", + "best_n = None\n", + "# alpha = 0.7\n", + "beta = 0.05\n", + "\n", + "for eps in epsilons:\n", + " for n in range(2, 30):\n", + " labels = DBSCAN(eps=eps, min_samples=n).fit(target_df).labels_\n", + " \n", + " n_unique = np.unique(labels)\n", + " n_outliers = len(labels[labels == -1])\n", + " \n", + " if n_outliers == len(labels) or len(n_unique) < 2:\n", + " continue\n", + " \n", + " # Encourage more clustering and discourage more outliers.\n", + " score = silhouette_score(target_df, labels) + (len(labels) - n_outliers)/n_outliers\n", + " \n", + " if score > best_score:\n", + " best_score = score\n", + " best_eps = eps\n", + " best_n = n\n", + "\n", + "print(f\"{best_score=}, {best_n=}, {best_eps=}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e39b41ba", + "metadata": {}, + "outputs": [], + "source": [ + "# 0.35 is a good value\n", + "\n", + "'''\n", + "allCEO = DBSCAN(eps=0.52, min_samples=2)\n", + "durham: DBSCAN(eps=best_eps, min_samples=2)\n", + "masscec: min_samples=2, eps=0.986724482988659\n", + "'''\n", + "\n", + "cl2 = DBSCAN(eps=best_eps, min_samples=2).fit(target_df)\n", + "# cl2 = KMeans(n_clusters=5).fit(target_df)\n", + "\n", + "Counter(cl2.labels_)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1dbf8763", + "metadata": {}, + "outputs": [], + "source": [ + "from sklearn.decomposition import PCA\n", + "\n", + "tsfm = PCA(n_components=2).fit_transform(target_df)\n", + "\n", + "fig, ax = plt.subplots()\n", + "sns.scatterplot(x=tsfm[:,0], y=tsfm[:,1], c=cl2.labels_)\n", + "ax.set(xlabel='Latent Dim 0', ylabel='Latent Dim 1')\n", + "plt.savefig(f'../outputs/{CURRENT_DB}__Fig2__PCA_w_colors.png', dpi=300)\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1e444316", + "metadata": {}, + "outputs": [], + "source": [ + "print(df.columns.tolist())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f0bc09b9", + "metadata": {}, + "outputs": [], + "source": [ + "# Per-cluster users.\n", + "from sklearn.preprocessing import OneHotEncoder\n", + "from sklearn.preprocessing import MinMaxScaler\n", + "from sklearn.ensemble import IsolationForest\n", + "from sklearn.svm import OneClassSVM\n", + "from sklearn.neighbors import LocalOutlierFactor\n", + "from sklearn.tree import DecisionTreeClassifier\n", + "\n", + "\n", + "demographic_cols = {\n", + " 'Stage_database': [\n", + " 'has_drivers_license', 'is_student', 'is_paid', \n", + " 'income_category', 'n_residence_members', 'n_residents_u18', 'n_residents_with_license', \n", + " 'n_motor_vehicles', 'has_medical_condition', 'ft_job', 'multiple_jobs', \n", + " 'n_working_residents', \"highest_education_Bachelor's degree\", \n", + " 'highest_education_Graduate degree or professional degree', \n", + " 'highest_education_High school graduate or GED', 'highest_education_Less than a high school graduate', \n", + " 'highest_education_Prefer not to say', 'highest_education_Some college or associates degree', \n", + " 'primary_job_description_Clerical or administrative support', 'primary_job_description_Custodial', \n", + " 'primary_job_description_Education', 'primary_job_description_Food service', \n", + " 'primary_job_description_Linecook', \n", + " 'primary_job_description_Manufacturing, construction, maintenance, or farming', \n", + " 'primary_job_description_Medical/healthcare', 'primary_job_description_Non-profit program manager', \n", + " 'primary_job_description_Other', 'primary_job_description_Professional, managerial, or technical', \n", + " 'primary_job_description_Sales or service', 'primary_job_description_Self employed', \n", + " 'primary_job_description_food service', 'gender_Man', 'gender_Nonbinary/genderqueer/genderfluid', \n", + " 'gender_Prefer not to say', 'gender_Woman', 'gender_Woman;Nonbinary/genderqueer/genderfluid', \n", + " 'av_transit', 'av_no_trip', 'av_p_micro', 'av_s_micro', 'av_ridehail', 'av_unknown', 'av_walk', 'av_car', \n", + " 'av_s_car'\n", + " ] + [c for c in df.columns if 'age' in c],\n", + " 'durham': [\n", + " 'is_student', 'is_paid', 'has_drivers_license', \n", + " 'n_residents_u18', 'n_residence_members', 'income_category',\n", + " 'n_residents_with_license', 'n_working_residents', 'n_motor_vehicles', 'has_medical_condition', \n", + " 'ft_job', 'multiple_jobs', 'highest_education_bachelor_s_degree', \n", + " 'highest_education_graduate_degree_or_professional_degree', \n", + " 'highest_education_high_school_graduate_or_ged', 'highest_education_less_than_a_high_school_graduate', \n", + " 'highest_education_some_college_or_associates_degree', \n", + " 'primary_job_description_Clerical or administrative support', \n", + " 'primary_job_description_Manufacturing, construction, maintenance, or farming', \n", + " 'primary_job_description_Other', 'primary_job_description_Professional, Manegerial, or Technical', \n", + " 'primary_job_description_Sales or service', 'gender_man', \n", + " 'gender_non_binary_genderqueer_gender_non_confor', 'gender_woman', \n", + " 'av_walk', 'av_unknown', 'av_no_trip', 'av_p_micro', 'av_transit', 'av_car', 'av_ridehail', \n", + " 'av_s_micro', 'av_s_car'\n", + " ] + [c for c in df.columns if 'age' in c],\n", + " 'masscec': [\n", + " 'is_student', 'is_paid', 'has_drivers_license', 'n_residents_u18', 'n_residence_members', \n", + " 'income_category', 'n_residents_with_license', 'n_working_residents', 'n_motor_vehicles', \n", + " 'has_medical_condition', 'ft_job', 'multiple_jobs', 'highest_education_bachelor_s_degree', \n", + " 'highest_education_graduate_degree_or_professional_degree', \n", + " 'highest_education_high_school_graduate_or_ged', 'highest_education_less_than_a_high_school_graduate', \n", + " 'highest_education_prefer_not_to_say', 'highest_education_some_college_or_associates_degree', \n", + " 'primary_job_description_Clerical or administrative support', \n", + " 'primary_job_description_Manufacturing, construction, maintenance, or farming', \n", + " 'primary_job_description_Other', 'primary_job_description_Prefer not to say', \n", + " 'primary_job_description_Professional, Manegerial, or Technical', \n", + " 'primary_job_description_Sales or service', 'gender_man', 'gender_prefer_not_to_say', 'gender_woman', \n", + " 'av_p_micro', 'av_s_car', 'av_s_micro', 'av_transit', 'av_car', 'av_no_trip', 'av_unknown', \n", + " 'av_ridehail', 'av_walk'\n", + " ] + [c for c in df.columns if 'age' in c],\n", + "}\n", + "\n", + "\n", + "cluster_labels = cl2.labels_\n", + "demographics = df.groupby('user_id').first()[demographic_cols[CURRENT_DB]]\n", + "demographics = demographics.loc[target_df.index, :]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5a3c6355", + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "### DEMOGRAPHICS\n", + "\n", + "def entropy(x):\n", + " # Compute bincount, normalize over the entire size. Gives us probabilities.\n", + " p = np.unique(x, return_counts=True)[1]/len(x)\n", + " # Compute the enropy usnig the probabilities.\n", + " return -np.sum(p * np.log2(p))\n", + "\n", + "def preprocess_demo_data(df: pd.DataFrame):\n", + " return df\n", + "\n", + "\n", + "within_cluster_homogeneity = dict()\n", + "other_cluster_homogeneity = dict()\n", + "labels = cl2.labels_\n", + "\n", + "for cix in np.unique(labels):\n", + " within_cluster_homogeneity[cix] = dict()\n", + " users = target_df[labels == cix].index\n", + " data = demographics.loc[demographics.index.isin(users), :].reset_index(drop=True)\n", + " processed = preprocess_demo_data(data)\n", + " \n", + " for col in processed.columns:\n", + " # Numeric/ordinal values. Use std. to measure homogeneity.\n", + " if col in [\n", + " 'n_residence_members', 'n_residents_u18', 'n_working_residents', 'n_motor_vehicles',\n", + " 'n_residents_with_license', 'income_category'\n", + " ]:\n", + " within_cluster_homogeneity[cix][col] = processed[col].std()\n", + " else:\n", + " within_cluster_homogeneity[cix][col] = entropy(processed[col])\n", + "\n", + "# Compute average homogeneity across other clusters.\n", + "for cix in within_cluster_homogeneity.keys():\n", + " other_cluster_homogeneity[cix] = dict()\n", + " other_clusters = set(within_cluster_homogeneity.keys()) - set([cix])\n", + " for feature in within_cluster_homogeneity[cix].keys():\n", + " homogeneity_in_others = [within_cluster_homogeneity[x][feature] for x in other_clusters]\n", + " other_cluster_homogeneity[cix][feature] = np.mean(homogeneity_in_others)\n", + "\n", + " \n", + "# Compute contrastive homogeneity\n", + "# CH = homogeneity within cluster / average homogeneity across other clusters\n", + "for cix in within_cluster_homogeneity.keys():\n", + " ch_scores = list()\n", + " print(f\"For cluster {cix}:\")\n", + " for feature in within_cluster_homogeneity[cix].keys():\n", + " feature_ch = within_cluster_homogeneity[cix][feature]/(other_cluster_homogeneity[cix][feature] + 1e-6)\n", + " ch_scores.append((feature, feature_ch))\n", + " \n", + " ch_df = pd.DataFrame(ch_scores, columns=['feature', 'ch']).sort_values(by=['ch']).head(4)\n", + " \n", + " # Display actual values.\n", + " users = target_df[labels == cix].index\n", + " data = demographics.loc[demographics.index.isin(users), :].reset_index(drop=True)\n", + " processed = preprocess_demo_data(data)\n", + " \n", + " display(ch_df)\n", + " print()\n", + " filtered = processed.loc[:, processed.columns.isin(ch_df.feature)][ch_df.feature]\n", + " filtered_features = ch_df.feature.tolist()\n", + " \n", + " fig, ax = plt.subplots(nrows=2, ncols=2, figsize=(12, 10))\n", + " for i, a in enumerate(ax.flatten()):\n", + " sns.histplot(filtered[filtered_features[i]], ax=a, stat=\"percent\")\n", + " plt.tight_layout()\n", + " plt.savefig(f\"{CURRENT_DB}_{cix}_Demographic_consistency.png\", dpi=300)\n", + " plt.show()\n", + " print()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "580bbd86", + "metadata": {}, + "outputs": [], + "source": [ + "from scipy.stats import iqr\n", + "\n", + "def get_trip_summary_df(users, df):\n", + " '''\n", + " 1. df = a huge dataframe of user-trips. Each row is a trip.\n", + " 2. every trip is divided into sections: [walk, transit, walk]\n", + " 3. Each section has a corresponding distance and duration: [m1, m2, m3], [t1, t2, t3], [d1, d2, d3]\n", + " 4. What we are doing is only considering the mode, distance, and duration of the section with the largest distance\n", + " '''\n", + " \n", + " costs = [c for c in df.columns if 'av_' in c]\n", + " \n", + " mode_coverage = df.groupby(['user_id', 'section_mode_argmax'])[\n", + " ['section_duration_argmax', 'section_distance_argmax', 'mph'] + costs\n", + " ].agg(['mean', 'median']).unstack()\n", + " \n", + " global_stats = df.groupby('user_id')[['duration', 'distance']].agg(\n", + " ['mean', 'median']\n", + " )\n", + "\n", + " mode_coverage.columns = mode_coverage.columns.map('_'.join)\n", + " global_stats.columns = global_stats.columns.map('_'.join)\n", + " \n", + " # return mode_coverage\n", + " return mode_coverage.merge(right=global_stats, left_index=True, right_index=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "92ad2485", + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "## TRIP SUMMARIES\n", + "\n", + "# Per-cluster users.\n", + "from sklearn.preprocessing import MinMaxScaler, StandardScaler\n", + "from sklearn.ensemble import IsolationForest\n", + "from sklearn.svm import OneClassSVM\n", + "from sklearn.neighbors import LocalOutlierFactor\n", + "from sklearn.feature_selection import SelectKBest, mutual_info_classif\n", + "\n", + "labels = cl2.labels_\n", + "\n", + "def get_data(cix):\n", + " users = target_df.iloc[labels == cix, :].index\n", + " \n", + " # Compute trip summaries.\n", + " X = df.loc[df.user_id.isin(users), [\n", + " 'section_distance_argmax', 'duration', 'distance', 'section_mode_argmax',\n", + " 'section_duration_argmax', 'mph', 'target', 'user_id'\n", + " ] + [c for c in df.columns if 'cost_' in c]].reset_index(drop=True)\n", + " \n", + " # Compute the target distribution and select the argmax.\n", + " target_distribution = X.target.value_counts(ascending=False, normalize=True)\n", + " target_distribution.rename(index=MAP, inplace=True)\n", + " \n", + " # Caution - this summary df has NaNs. Use nanstd() to compute nan-aware std.\n", + " subset = get_trip_summary_df(users, X)\n", + " \n", + " norm_subset = pd.DataFrame(\n", + " MinMaxScaler().fit_transform(subset),\n", + " columns=subset.columns, index=subset.index\n", + " )\n", + " \n", + " return norm_subset, target_distribution\n", + "\n", + "\n", + "in_cluster_homogeneity = dict()\n", + "out_cluster_homogeneity = dict()\n", + "\n", + "for cluster_ix in np.unique(labels):\n", + " in_cluster_homogeneity[cluster_ix] = dict()\n", + " norm_subset, _ = get_data(cluster_ix)\n", + " for feature in norm_subset.columns:\n", + " in_cluster_homogeneity[cluster_ix][feature] = np.nanstd(norm_subset[feature])\n", + "\n", + "for cix in in_cluster_homogeneity.keys():\n", + " out_cluster_homogeneity[cix] = dict()\n", + " oix = set(labels) - set([cix])\n", + " for feature in norm_subset.columns:\n", + " out_cluster_homogeneity[cix][feature] = np.nanmean([in_cluster_homogeneity[x].get(feature, np.nan) for x in oix])\n", + "\n", + "# Now, compute the per-cluster homogeneity.\n", + "for cix in in_cluster_homogeneity.keys():\n", + " ch = list()\n", + " for feature in in_cluster_homogeneity[cix].keys():\n", + " if feature in in_cluster_homogeneity[cix] and feature in out_cluster_homogeneity[cix]:\n", + " ratio = in_cluster_homogeneity[cix][feature] / (out_cluster_homogeneity[cix][feature] + 1e-6)\n", + " ch.append([feature, ratio])\n", + " \n", + " ch_df = pd.DataFrame(ch, columns=['feature', 'ch']).sort_values(by=['ch']).head(4)\n", + " data, target_dist = get_data(cix)\n", + " \n", + " features = ch_df.feature.tolist()\n", + " \n", + " print(f\"For cluster {cix}:\")\n", + " display(target_dist)\n", + " display(ch_df)\n", + " \n", + " fig, ax = plt.subplots(nrows=2, ncols=2, figsize=(12, 10))\n", + " for i, a in enumerate(ax.flatten()):\n", + " sns.histplot(data[features[i]], ax=a, stat=\"percent\")\n", + " plt.tight_layout()\n", + " plt.savefig(f\"{CURRENT_DB}_{cix}_Trip_consistency.png\", dpi=300)\n", + " plt.show()\n", + " print()\n", + " \n", + " print(50*'=')" + ] + }, + { + "cell_type": "markdown", + "id": "4992ff45", + "metadata": {}, + "source": [ + "## Now check the combined homogeneity score" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a8723e3d", + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "ic, oc = dict(), dict()\n", + "\n", + "labels = cl2.labels_\n", + "TOP_K = 3\n", + "\n", + "\n", + "for cix in np.unique(labels):\n", + " ic[cix] = dict()\n", + " \n", + " # Trip characteristics.\n", + " norm_subset, _ = get_data(cix)\n", + " for feature in norm_subset.columns:\n", + " ic[cix][feature] = np.nanstd(norm_subset[feature])\n", + " \n", + " # Demographics.\n", + " users = target_df[labels == cix].index\n", + " data = demographics.loc[demographics.index.isin(users), :].reset_index(drop=True)\n", + " processed = preprocess_demo_data(data)\n", + " \n", + " for col in processed.columns:\n", + " # Numeric/ordinal values. Use std. to measure homogeneity.\n", + " if col in [\n", + " 'n_residence_members', 'n_residents_u18', 'n_working_residents', 'n_motor_vehicles',\n", + " 'n_residents_with_license', 'income_category'\n", + " ]:\n", + " ic[cix][col] = np.nanstd(processed[col])\n", + " else:\n", + " ic[cix][col] = entropy(processed[col])\n", + "\n", + "for cix in ic.keys():\n", + " oc[cix] = dict()\n", + " oix = set(labels) - set([cix])\n", + " for feature in ic[cix].keys():\n", + " oc[cix][feature] = np.nanmean([ic[x].get(feature, np.nan) for x in oix])\n", + "\n", + "per_cluster_most_homogeneous = dict()\n", + "\n", + "# Now, compute the per-cluster homogeneity.\n", + "ax_ix = 0\n", + "for cix in ic.keys():\n", + "\n", + " print(f\"For cluster {cix}:\")\n", + "\n", + " # For each, cluster, we will have (TOP_K x n_clusters) figures.\n", + " fig, ax = plt.subplots(nrows=TOP_K, ncols=len(ic.keys()), figsize=(12, 8))\n", + "\n", + " other_ix = set(ic.keys()) - set([cix])\n", + " \n", + " ch = list()\n", + " for feature in ic[cix].keys():\n", + " if feature in oc[cix]:\n", + " ratio = ic[cix][feature] / (oc[cix][feature] + 1e-6)\n", + " ch.append([feature, ratio])\n", + " \n", + " # Just the top k.\n", + " ch_df = pd.DataFrame(ch, columns=['feature', 'ch']).sort_values(by=['ch']).reset_index(drop=True).head(TOP_K)\n", + "\n", + " figure_data = dict()\n", + " \n", + " # Get the actual trip summary data.\n", + " trip_summary_data, target_dist = get_data(cix)\n", + " \n", + " # Get the actual demographic data.\n", + " users = target_df[labels == cix].index\n", + " data = demographics.loc[demographics.index.isin(users), :].reset_index(drop=True)\n", + " processed = preprocess_demo_data(data)\n", + "\n", + " # Left-most subplot will be that of the current cluster's feature.\n", + " for row_ix, row in ch_df.iterrows():\n", + " if row.feature in trip_summary_data.columns:\n", + " sns.histplot(trip_summary_data[row.feature], ax=ax[row_ix][0], stat='percent').set_title(\"Current cluster\")\n", + " else:\n", + " sns.histplot(processed[row.feature], ax=ax[row_ix][0], stat='percent').set_title(\"Current cluster\")\n", + " ax[row_ix][0].set_xlabel(ax[row_ix][0].get_xlabel(), fontsize=8)\n", + " ax[row_ix][0].set_ylim(0., 100.)\n", + "\n", + " offset_col_ix = 1\n", + " ## Now, others.\n", + " for oix in other_ix:\n", + " # Get the actual trip summary data.\n", + " other_summary_data, _ = get_data(oix)\n", + " \n", + " # Get the actual demographic data.\n", + " users = target_df[labels == oix].index\n", + " data = demographics.loc[demographics.index.isin(users), :].reset_index(drop=True)\n", + " other_demo = preprocess_demo_data(data)\n", + "\n", + " for row_ix, row in ch_df.iterrows():\n", + " if row.feature in other_summary_data.columns:\n", + " sns.histplot(other_summary_data[row.feature], ax=ax[row_ix][offset_col_ix], stat='percent').set_title(f\"Cluster {oix}\")\n", + " else:\n", + " sns.histplot(other_demo[row.feature], ax=ax[row_ix][offset_col_ix], stat='percent').set_title(f\"Cluster {oix}\")\n", + " ax[row_ix][offset_col_ix].set_xlabel(ax[row_ix][offset_col_ix].get_xlabel(), fontsize=8)\n", + " ax[row_ix][offset_col_ix].set_ylim(0., 100.)\n", + " \n", + " offset_col_ix += 1\n", + " \n", + " plt.tight_layout()\n", + " plt.savefig(f\"../outputs/{CURRENT_DB}_cluster{cix}_combined_features.png\", dpi=300)\n", + " plt.show()\n", + " print(50 * '=')" + ] + }, + { + "cell_type": "markdown", + "id": "24a80f68", + "metadata": {}, + "source": [ + "## Try a different clustering technique? (Unexplored)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d0288db8", + "metadata": {}, + "outputs": [], + "source": [ + "from sklearn.cluster import AffinityPropagation\n", + "\n", + "best_score = -np.inf\n", + "best_params = None" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1b14ad0c", + "metadata": {}, + "outputs": [], + "source": [ + "cls = AffinityPropagation(random_state=13210).fit(target_df)\n", + "labels = cls.labels_\n", + "\n", + "print(labels)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2562bbb6-66eb-4283-8c08-6e20a0b2ade5", + "metadata": {}, + "outputs": [], + "source": [ + "center_embeddings = cls.cluster_centers_\n", + "centers_proj = PCA(n_components=2).fit_transform(center_embeddings)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c7aad38a", + "metadata": {}, + "outputs": [], + "source": [ + "fig, ax = plt.subplots()\n", + "sns.scatterplot(x=tsfm[:,0], y=tsfm[:,1], c=cls.labels_, ax=ax)\n", + "ax.scatter(x=centers_proj[:,0], y=centers_proj[:,1], marker='X', c='red', alpha=0.5)\n", + "ax.set(xlabel='Latent Dim 0', ylabel='Latent Dim 1')\n", + "# plt.legend([str(x) for x in ap_labels], loc='best')\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "39ce0238-b3f2-4f46-a52f-13e3160cc52f", + "metadata": {}, + "outputs": [], + "source": [ + "def get_data2(cix, labels):\n", + " users = target_df.iloc[labels == cix, :].index\n", + " \n", + " # Compute trip summaries.\n", + " X = df.loc[df.user_id.isin(users), [\n", + " 'section_distance_argmax', 'section_duration_argmax',\n", + " 'section_mode_argmax', 'distance',\n", + " 'duration', 'mph', 'user_id', 'target'\n", + " ]]\n", + " \n", + " # Compute the target distribution and select the argmax.\n", + " target_distribution = X.target.value_counts(ascending=False, normalize=True)\n", + " target_distribution.rename(index=MAP, inplace=True)\n", + " \n", + " # Caution - this summary df has NaNs. Use nanstd() to compute nan-aware std.\n", + " subset = get_trip_summary_df(users, X)\n", + " \n", + " norm_subset = pd.DataFrame(\n", + " MinMaxScaler().fit_transform(subset),\n", + " columns=subset.columns, index=subset.index\n", + " )\n", + " \n", + " return norm_subset, target_distribution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ec27cf29", + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "## Analaysis for this data.\n", + "\n", + "ic, oc = dict(), dict()\n", + "labels = cls.labels_\n", + "\n", + "for cix in np.unique(labels):\n", + " users = target_df[labels == cix].index\n", + " \n", + " ic[cix] = dict()\n", + " \n", + " # Trip characteristics.\n", + " norm_subset, _ = get_data2(cix, labels)\n", + " for feature in norm_subset.columns:\n", + " ic[cix][feature] = np.nanstd(norm_subset[feature])\n", + " \n", + " # Demographics.\n", + " data = demographics.loc[demographics.index.isin(users), :].reset_index(drop=True)\n", + " processed = preprocess_demo_data(data)\n", + " \n", + " for col in processed.columns:\n", + " # Numeric/ordinal values. Use std. to measure homogeneity.\n", + " if col == 'age' or col == 'income_category' or col == 'n_working_residents':\n", + " ic[cix][col] = np.nanstd(processed[col])\n", + " else:\n", + " ic[cix][col] = entropy(processed[col])\n", + "\n", + "for cix in ic.keys():\n", + " oc[cix] = dict()\n", + " oix = set(labels) - set([cix])\n", + " for feature in ic[cix].keys():\n", + " oc[cix][feature] = np.nanmean([ic[x].get(feature, np.nan) for x in oix])\n", + "\n", + "# # Now, compute the per-cluster homogeneity.\n", + "# for cix in ic.keys():\n", + " \n", + "# users = users = target_df[labels == cix].index\n", + "# norm_subset, target_dist = get_data(cix, labels)\n", + "# data = demographics.loc[demographics.index.isin(users), :].reset_index(drop=True)\n", + "# processed = preprocess_demo_data(data)\n", + " \n", + "# concat = processed.merge(norm_subset, left_index=True, right_index=True)\n", + " \n", + "# ch = list()\n", + "# for feature in ic[cix].keys():\n", + "# ratio = ic[cix][feature] / (oc[cix][feature] + 1e-6)\n", + "# ch.append([feature, ratio])\n", + " \n", + "# ch_df = pd.DataFrame(ch, columns=['feature', 'ch']).sort_values(by=['ch']).head(TOP_K).reset_index(drop=True)\n", + "\n", + "\n", + "# Now, compute the per-cluster homogeneity.\n", + "ax_ix = 0\n", + "for cix in ic.keys():\n", + "\n", + " print(f\"For cluster {cix}:\")\n", + "\n", + " # For each, cluster, we will have (TOP_K x n_clusters) figures.\n", + " fig, ax = plt.subplots(nrows=5, ncols=len(ic.keys()), figsize=(12, 8))\n", + "\n", + " other_ix = set(ic.keys()) - set([cix])\n", + " \n", + " ch = list()\n", + " for feature in ic[cix].keys():\n", + " ratio = ic[cix][feature] / (oc[cix][feature] + 1e-6)\n", + " ch.append([feature, ratio])\n", + " \n", + " # Just the top k.\n", + " ch_df = pd.DataFrame(ch, columns=['feature', 'ch']).sort_values(by=['ch']).reset_index(drop=True).head(5)\n", + " figure_data = dict()\n", + " \n", + " # Get the actual trip summary data.\n", + " trip_summary_data, target_dist = get_data(cix)\n", + "\n", + " display(target_dist)\n", + " \n", + " # Get the actual demographic data.\n", + " users = target_df[labels == cix].index\n", + " data = demographics.loc[demographics.index.isin(users), :].reset_index(drop=True)\n", + " processed = preprocess_demo_data(data)\n", + "\n", + " # Left-most subplot will be that of the current cluster's feature.\n", + " for row_ix, row in ch_df.iterrows():\n", + " if row.feature in trip_summary_data.columns:\n", + " sns.histplot(trip_summary_data[row.feature], ax=ax[row_ix][0], stat='percent').set_title(\"Current cluster\")\n", + " else:\n", + " sns.histplot(processed[row.feature], ax=ax[row_ix][0], stat='percent').set_title(\"Current cluster\")\n", + " ax[row_ix][0].set_xlabel(ax[row_ix][0].get_xlabel(), fontsize=6)\n", + " ax[row_ix][0].set_ylim(0., 100.)\n", + "\n", + " offset_col_ix = 1\n", + " ## Now, others.\n", + " for oix in other_ix:\n", + " # Get the actual trip summary data.\n", + " other_summary_data, _ = get_data(oix)\n", + " \n", + " # Get the actual demographic data.\n", + " users = target_df[labels == oix].index\n", + " data = demographics.loc[demographics.index.isin(users), :].reset_index(drop=True)\n", + " other_demo = preprocess_demo_data(data)\n", + "\n", + " for row_ix, row in ch_df.iterrows():\n", + " if row.feature in other_summary_data.columns:\n", + " sns.histplot(other_summary_data[row.feature], ax=ax[row_ix][offset_col_ix], stat='percent').set_title(f\"Cluster {oix}\")\n", + " else:\n", + " sns.histplot(other_demo[row.feature], ax=ax[row_ix][offset_col_ix], stat='percent').set_title(f\"Cluster {oix}\")\n", + " ax[row_ix][offset_col_ix].set_xlabel(ax[row_ix][offset_col_ix].get_xlabel(), fontsize=6)\n", + " ax[row_ix][offset_col_ix].set_ylim(0., 100.)\n", + " \n", + " offset_col_ix += 1\n", + "\n", + " plt.tight_layout()\n", + " plt.show()\n", + " print(50 * '=')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c0b642db", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "emission", + "language": "python", + "name": "emission" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.16" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/viz_scripts/.DS_Store b/viz_scripts/.DS_Store index 147fc0a..cea5658 100644 Binary files a/viz_scripts/.DS_Store and b/viz_scripts/.DS_Store differ diff --git a/viz_scripts/Dockerfile b/viz_scripts/Dockerfile deleted file mode 100644 index e4c3c37..0000000 --- a/viz_scripts/Dockerfile +++ /dev/null @@ -1,42 +0,0 @@ -# python 3 -FROM emission/e-mission-server.dev.server-only:4.0.0 -ENV SERVER_REPO=https://github.com/e-mission/e-mission-server.git -ENV SERVER_BRANCH=join_redirect_to_static - -VOLUME /plots - -ADD docker/environment36.dashboard.additions.yml / - -RUN /bin/bash -c "/clone_server.sh" - -WORKDIR /usr/src/app - -RUN /bin/bash -c "cd e-mission-server && source setup/activate.sh && conda env update --name emission --file setup/environment36.notebook.additions.yml" -RUN /bin/bash -c "cd e-mission-server && source setup/activate.sh && conda env update --name emission --file /environment36.dashboard.additions.yml" - -RUN mkdir -p /usr/src/app/saved-notebooks -WORKDIR /usr/src/app/saved-notebooks - -COPY auxiliary_files ./auxiliary_files -COPY bin ./bin -COPY *.ipynb . -COPY *.py . - -# Delete all test packages since they generate false positives in the vulnerability scan -# e.g. -# root/miniconda-4.12.0/pkgs/conda-4.12.0-py38h06a4308_0/info/test/tests/data/env_metadata/py27-osx-no-binary/lib/python2.7/site-packages/requests-2.19.1-py2.7.egg-info/PKG-INFO -# root/miniconda-4.12.0/pkgs/conda-4.12.0-py38h06a4308_0/info/test/tests/data/env_metadata/py36-osx-whl/lib/python3.6/site-packages/Django-2.1.dist-info/METADATA -# root/miniconda-4.12.0/pkgs/conda-4.12.0-py38h06a4308_0/info/test/tests/data/env_metadata/py36-osx-whl/lib/python3.6/site-packages/Scrapy-1.5.1.dist-info/METADATA - -RUN /bin/bash -c "find /root/miniconda-*/pkgs -wholename \*info/test\* -type d | xargs rm -rf" - -WORKDIR /usr/src/app - -ADD docker/start_notebook.sh /usr/src/app/.docker/start_notebook.sh -RUN chmod u+x /usr/src/app/.docker/start_notebook.sh - -ADD docker/crontab /usr/src/app/crontab - -EXPOSE 8888 - -CMD ["/bin/bash", "/usr/src/app/.docker/start_notebook.sh"] diff --git a/viz_scripts/auxiliary_files/energy_intensity.csv b/viz_scripts/auxiliary_files/energy_intensity.csv deleted file mode 100644 index c203241..0000000 --- a/viz_scripts/auxiliary_files/energy_intensity.csv +++ /dev/null @@ -1,17 +0,0 @@ -mode,fuel,(kWH)/trip,EI(kWH/PMT),energy_intensity_factor,energy_intensity_units,CO2_factor,CO2_factor_units -"Gas Car, drove alone",gasoline,0,,5170,BTU/PMT,157.2,lb_CO2/MMBTU -"Gas Car, with others",gasoline,0,,2585,BTU/PMT,157.2,lb_CO2/MMBTU -"E-car, drove alone",electric,0,0.25,0.25,kWH/PMT,1166,lb_CO2/MWH -"E-car, with others",electric,0,0.125,0.125,kWH/PMT,1166,lb_CO2/MWH -Taxi/Uber/Lyft,gasoline,0,,7214,BTU/PMT,157.2,lb_CO2/MMBTU -Bus,diesel,0,,4560,BTU/PMT,161.3,lb_CO2/MMBTU -Free Shuttle,diesel,0,,4560,BTU/PMT,161.3,lb_CO2/MMBTU -Train,electric,0,0.37,0.37,kWH/PMT,1166,lb_CO2/MWH -Scooter share,electric,0.0041,0.027,0.027,kWH/PMT,1166,lb_CO2/MWH -E-bike,electric,0,0.022,0.022,kWH/PMT,1166,lb_CO2/MWH -Bikeshare,human_powered,0.09,0,0,kWH/PMT,1166,lb_CO2/MWH -Walk,human_powered,0,,0,,0,0 -Skate board,human_powered,0,,0,,0,0 -Regular Bike,human_powered,0,,0,,0,0 -Not a Trip,none,0,,0,,0,0 -No Travel,none,0,,0,,0,0 diff --git a/viz_scripts/auxiliary_files/mode_labels.csv b/viz_scripts/auxiliary_files/mode_labels.csv deleted file mode 100644 index 9397d75..0000000 --- a/viz_scripts/auxiliary_files/mode_labels.csv +++ /dev/null @@ -1,32 +0,0 @@ -replaced_mode,mode_confirm,mode_clean -drove_alone,drove_alone,"Gas Car, drove alone" -e_car_drove_alone,e_car_drove_alone,"E-car, drove alone" -work_vehicle,work_vehicle,"Gas Car, drove alone" -bus,bus,Bus -train,train,Train -free_shuttle,free_shuttle,Free Shuttle -"train,_bus and walk","train,_bus and walk",Train -train_and pilot e-bike,train_and pilot e-bike,Train -taxi,taxi,Taxi/Uber/Lyft -friend_picked me up,friend_picked me up,"Gas Car, with others" -carpool_w/ friend to work,carpool_w/ friend to work,"Gas Car, with others" -friend_carpool to work,friend_carpool to work,"Gas Car, with others" -carpool_to work,carpool_to work,"Gas Car, with others" -friend/co_worker carpool,friend/co_worker carpool,"Gas Car, with others" -carpool_to lunch,carpool_to lunch,"Gas Car, with others" -carpool,carpool,"Gas Car, with others" -carpool_for lunch,carpool_for lunch,"Gas Car, with others" -carpool_lunch,carpool_lunch,"Gas Car, with others" -shared_ride,shared_ride,"Gas Car, with others" -e_car_shared_ride,e_car_shared_ride,"E-car, with others" -bikeshare,bikeshare,Bikeshare -scootershare,scootershare,Scooter share -pilot_ebike,pilot_ebike,E-bike -e-bike,e-bike,E-bike -walk,walk,Walk -skateboard,skateboard,Skate board -bike,bike,Regular Bike -the_friend who drives us to work was running errands after the shift before dropping me off. not a trip of mine.,the_friend who drives us to work was running errands after the shift before dropping me off. not a trip of mine.,Not a Trip -not_a_trip,not_a_trip,Not a Trip -no_travel,,No Travel -same_mode,,Same Mode diff --git a/viz_scripts/auxiliary_files/purpose_labels.csv b/viz_scripts/auxiliary_files/purpose_labels.csv deleted file mode 100644 index 298e8aa..0000000 --- a/viz_scripts/auxiliary_files/purpose_labels.csv +++ /dev/null @@ -1,47 +0,0 @@ -purpose_confirm,bin_purpose -work_travel,Work -work,Work -home,Home -meal,Meal -shopping,Shopping -personal_med,Personal/Medical -exercise,Recreation/Exercise -transit_transfer,Transit transfer -pick_drop,Pick-up/Drop off -entertainment,Entertainment/Social -car_mechanic,Other -school,School -revisado_bike,Other -placas_de carro,Other -community_walk,Entertainment/Social -gardening,Entertainment/Social -visiting,Entertainment/Social -church,Religious -community_garden,Entertainment/Social -community_meeting,Entertainment/Social -visit_a friend,Entertainment/Social -aseguranza,Other -meeting_bike,Entertainment/Social -gas_station,Other -iglesia,Religious -curso,School -mi_hija recién aliviada,Entertainment/Social -servicio_comunitario,Entertainment/Social -pago_de aseguranza,Other -grupo_comunitario,Entertainment/Social -caminata_comunitaria,Entertainment/Social -bank,Other -religious,Religious -no_travel,No travel -work_break - short walk,Entertainment/Social -work_- lunch break,Meal -friend_was running errands before dropping me off after work,Other -"multiple_errands, etc.",Other -lunch_break,Meal -break,Entertainment/Social -pet,Entertainment/Social -recording_performance at park,Entertainment/Social -not_a trip,not_a_trip -on_the way home,Home -other,Other -nan,nan \ No newline at end of file diff --git a/viz_scripts/bin/generate_plots.py b/viz_scripts/bin/generate_plots.py deleted file mode 100644 index 966e5fd..0000000 --- a/viz_scripts/bin/generate_plots.py +++ /dev/null @@ -1,87 +0,0 @@ -import nbclient -import nbformat -import nbparameterise as nbp -import argparse -import arrow -import requests -import json -import os -import sys - - -# Configuration settings to use for all generated plots by this instance -# This could also be specified as a parser argument, if we want to generate plots for all programs from one instance -# Full list is at -# https://github.com/e-mission/nrel-openpath-deploy-configs/tree/main/configs -STUDY_CONFIG = os.getenv('STUDY_CONFIG', "stage-program") - -parser = argparse.ArgumentParser(prog="generate_metrics") -parser.add_argument("plot_notebook", help="the notebook the generates the plot") -parser.add_argument("program", help="the program for the plot") -parser.add_argument("-d", "--date", nargs=2, type=int, - help="the year and month for the plot. Default: all previous days and months since program start + one combined for the program as a whole") - -args = parser.parse_args() - -# Read and use parameters from the unified config file on the e-mission Github page -download_url = "https://raw.githubusercontent.com/e-mission/nrel-openpath-deploy-configs/main/configs/" + STUDY_CONFIG + ".nrel-op.json" -print("About to download config from %s" % download_url) -r = requests.get(download_url) -if r.status_code is not 200: - print(f"Unable to download study config, status code: {r.status_code}") - sys.exit(1) -else: - dynamic_config = json.loads(r.text) - print(f"Successfully downloaded config with version {dynamic_config['version']} "\ - f"for {dynamic_config['intro']['translated_text']['en']['deployment_name']} "\ - f"and data collection URL {dynamic_config['server']['connectUrl']}") - -if dynamic_config['intro']['program_or_study'] == 'program': - mode_studied = dynamic_config['intro']['mode_studied'] -else: - mode_studied = None - -if args.date is None: - start_date = arrow.get(int(dynamic_config['intro']['start_year']), - int(dynamic_config['intro']['start_month']), 1) - end_date = arrow.get() -else: - start_date = arrow.get() - end_date = start_date - -compute_range = list(arrow.Arrow.range('month', start_date, end_date)) - -print(f"Running at {arrow.get()} with args {args} for range {compute_range[0], compute_range[-1]}") - -with open(args.plot_notebook) as f: - nb = nbformat.read(f, as_version=4) - -# Get a list of Parameter objects -orig_parameters = nbp.extract_parameters(nb) - -# We will be recomputing values for multiple months -# So let's make a common function to invoke -def compute_for_date(month, year): - params = nbp.parameter_values( - orig_parameters, - year=year, - month=month, - program=args.program, - study_type=dynamic_config['intro']['program_or_study'], - mode_of_interest=mode_studied, - include_test_users=dynamic_config.get('metrics', {}).get('include_test_users', False)) - - print(f"Running at {arrow.get()} with params {params}") - - # Make a notebook object with these definitions - new_nb = nbp.replace_definitions(nb, params, execute=False) - - # Execute the notebook with the new parameters - nbclient.execute(new_nb) - -# Compute the overall metrics -compute_for_date(None, None) - -# Compute for every month until now -for month_year in compute_range: - compute_for_date(month_year.month, month_year.year) diff --git a/viz_scripts/bin/update_mappings.py b/viz_scripts/bin/update_mappings.py deleted file mode 100644 index 906bfb2..0000000 --- a/viz_scripts/bin/update_mappings.py +++ /dev/null @@ -1,16 +0,0 @@ -# In a docker setup, run as -# sudo run_from_host/update_mappings.sh -# -# -import nbclient -import nbformat -import argparse - -parser = argparse.ArgumentParser(prog="update_mappings") -parser.add_argument("mapping_notebook", help="the notebook the stores the mappings") - -args = parser.parse_args() - -with open(args.mapping_notebook) as f: - nb = nbformat.read(f, as_version=4) - nbclient.execute(nb) diff --git a/viz_scripts/docker/Dockerfile.dev b/viz_scripts/docker/Dockerfile.dev deleted file mode 100644 index f6c10b1..0000000 --- a/viz_scripts/docker/Dockerfile.dev +++ /dev/null @@ -1,24 +0,0 @@ -# python 3 -FROM emission/e-mission-server.dev.server-only:4.0.0 -ARG SERVER_REPO=https://github.com/aGuttman/e-mission-server.git -ARG SERVER_BRANCH=dashboard-dependencies - -VOLUME /plots - -ADD docker/environment36.dashboard.additions.yml / - -RUN /bin/bash -c "/clone_server.sh" - -WORKDIR /usr/src/app - -RUN /bin/bash -c "cd e-mission-server && source setup/activate.sh && conda env update --name emission --file setup/environment36.notebook.additions.yml" -RUN /bin/bash -c "cd e-mission-server && source setup/activate.sh && conda env update --name emission --file /environment36.dashboard.additions.yml" - -ADD docker/start_notebook.sh /usr/src/app/.docker/start_notebook.sh -RUN chmod u+x /usr/src/app/.docker/start_notebook.sh - -ADD docker/crontab /usr/src/app/crontab - -EXPOSE 8888 - -CMD ["/bin/bash", "/usr/src/app/.docker/start_notebook.sh"] diff --git a/viz_scripts/docker/crontab b/viz_scripts/docker/crontab deleted file mode 100644 index d02d3d5..0000000 --- a/viz_scripts/docker/crontab +++ /dev/null @@ -1,8 +0,0 @@ -0 7 * * * python bin/update_mappings.py mapping_dictionaries.ipynb >> /var/log/intake.stdinout 2>&1 -0 8 * * * python bin/generate_plots.py generic_metrics.ipynb default >> /var/log/intake.stdinout 2>&1 -0 8 * * * python bin/generate_plots.py generic_timeseries.ipynb default >> /var/log/intake.stdinout 2>&1 -0 8 * * * python bin/generate_plots.py mode_specific_metrics.ipynb default >> /var/log/intake.stdinout 2>&1 -0 8 * * * python bin/generate_plots.py mode_specific_timeseries.ipynb default >> /var/log/intake.stdinout 2>&1 -0 8 * * * python bin/generate_plots.py energy_calculations.ipynb default >> /var/log/intake.stdinout 2>&1 -# For testing only -# */5 * * * * python bin/generate_plots.py mode_purpose_share.ipynb default >> /var/log/intake.stdinout 2>&1 diff --git a/viz_scripts/docker/environment36.dashboard.additions.yml b/viz_scripts/docker/environment36.dashboard.additions.yml deleted file mode 100644 index 76d37e7..0000000 --- a/viz_scripts/docker/environment36.dashboard.additions.yml +++ /dev/null @@ -1,9 +0,0 @@ -name: emission -channels: -- conda-forge -- defaults -dependencies: -- seaborn=0.11.1 -- pip: - - nbparameterise - - devcron diff --git a/viz_scripts/docker/generate_plots.sh b/viz_scripts/docker/generate_plots.sh deleted file mode 100755 index 6674f4a..0000000 --- a/viz_scripts/docker/generate_plots.sh +++ /dev/null @@ -1,5 +0,0 @@ -cd e-mission-server -source setup/activate.sh -cd ../saved-notebooks - -PYTHONPATH=/usr/src/app/e-mission-server python bin/generate_plots.py $* diff --git a/viz_scripts/docker/load_mongodump.sh b/viz_scripts/docker/load_mongodump.sh deleted file mode 100644 index fb17cd7..0000000 --- a/viz_scripts/docker/load_mongodump.sh +++ /dev/null @@ -1,9 +0,0 @@ -MONGODUMP_FILE=$1 - -echo "Copying file to docker container" -docker cp $MONGODUMP_FILE em-public-dashboard_db_1:/tmp - -FILE_NAME=`basename $MONGODUMP_FILE` - -echo "Restoring the dump from $FILE_NAME" -docker exec -e MONGODUMP_FILE=$FILE_NAME em-public-dashboard_db_1 bash -c 'cd /tmp && tar xvf $MONGODUMP_FILE && mongorestore' diff --git a/viz_scripts/docker/start_notebook.sh b/viz_scripts/docker/start_notebook.sh deleted file mode 100755 index d08a5e9..0000000 --- a/viz_scripts/docker/start_notebook.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env bash -#Configure web server - -pushd /usr/src/app/e-mission-server - -#set database URL using environment variable -echo "DB host = "${DB_HOST} -if [ -z ${DB_HOST} ] ; then - local_host=`hostname -i` - sed "s_localhost_${local_host}_" conf/storage/db.conf.sample > conf/storage/db.conf -else - sed "s_localhost_${DB_HOST}_" conf/storage/db.conf.sample > conf/storage/db.conf -fi -popd - -### configure the saved-notebooks directory for persistent notebooks - -# Ensure that the database config is available so that we can connect to it -mkdir -p saved-notebooks/conf/storage -cp e-mission-server/conf/storage/db.conf saved-notebooks/conf/storage/db.conf -cat saved-notebooks/conf/storage/db.conf - -#set Web Server host using environment variable -echo "Web host = "${WEB_SERVER_HOST} - -# change python environment -pushd e-mission-server -pwd -source setup/activate.sh -conda env list -popd - -cd saved-notebooks - -# launch the notebook server -# tail -f /dev/null -if [ -z ${CRON_MODE} ] ; then - echo "Running notebook in docker, change host:port to localhost:47962 in the URL below" - PYTHONPATH=/usr/src/app/e-mission-server jupyter notebook --no-browser --ip=${WEB_SERVER_HOST} --allow-root -else - echo "Running crontab without user interaction, setting python path" - export PYTHONPATH=/usr/src/app/e-mission-server - # tail -f /dev/null - devcron ../crontab >> /var/log/cron.console.stdinout 2>&1 -fi diff --git a/viz_scripts/docker/update_mappings.sh b/viz_scripts/docker/update_mappings.sh deleted file mode 100755 index 866bfb7..0000000 --- a/viz_scripts/docker/update_mappings.sh +++ /dev/null @@ -1,5 +0,0 @@ -cd e-mission-server -source setup/activate.sh -cd ../saved-notebooks - -PYTHONPATH=/usr/src/app/e-mission-server python bin/update_mappings.py $* diff --git a/viz_scripts/energy_calculations.ipynb b/viz_scripts/energy_calculations.ipynb deleted file mode 100644 index a8efd6b..0000000 --- a/viz_scripts/energy_calculations.ipynb +++ /dev/null @@ -1,260 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "united-feeding", - "metadata": {}, - "source": [ - "## Generate Static Graphs" - ] - }, - { - "cell_type": "markdown", - "id": "outdoor-celebrity", - "metadata": {}, - "source": [ - "These are the input parameters for the notebook. They will be automatically changed when the scripts to generate monthly statistics are run. You can modify them manually to generate multiple plots locally as well.\n", - "\n", - "Pass in `None` to remove the filters and plot all data. This is not recommended for production settings, but might be useful for reports based on data snapshots." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "determined-matrix", - "metadata": {}, - "outputs": [], - "source": [ - "year = 2020\n", - "month = 11\n", - "program = \"default\"\n", - "study_type = \"program\"\n", - "mode_of_interest = \"e-bike\"\n", - "include_test_users = False" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "pharmaceutical-survival", - "metadata": {}, - "outputs": [], - "source": [ - "from collections import defaultdict\n", - "\n", - "import numpy as np\n", - "import pandas as pd\n", - "\n", - "from plots import *\n", - "import scaffolding\n", - "\n", - "sns.set_style(\"whitegrid\")\n", - "sns.set()\n", - "%matplotlib inline" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "furnished-silicon", - "metadata": {}, - "outputs": [], - "source": [ - "# Do not run this notebook at all unless it is for a program; nbclient will run up through this cell\n", - "if study_type != \"program\":\n", - " ipython = get_ipython()\n", - " ipython._showtraceback = scaffolding.no_traceback_handler\n", - " raise Exception(\"The plots in this notebook are only relevant to programs\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "nearby-fruit", - "metadata": {}, - "outputs": [], - "source": [ - "# Loading mapping dictionaries from mapping_dictionaries notebook\n", - "%store -r df_ei\n", - "%store -r dic_re\n", - "%store -r dic_pur\n", - "%store -r dic_fuel\n", - "\n", - "# convert a dictionary to a defaultdict\n", - "dic_re = defaultdict(lambda: 'Other',dic_re)\n", - "dic_pur = defaultdict(lambda: 'Other',dic_pur)\n", - "dic_fuel = defaultdict(lambda: 'Other',dic_fuel)" - ] - }, - { - "cell_type": "markdown", - "id": "parallel-patch", - "metadata": {}, - "source": [ - "## Collect Data From Database" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "sufficient-kingston", - "metadata": { - "scrolled": false - }, - "outputs": [], - "source": [ - "expanded_ct, file_suffix, quality_text, debug_df = scaffolding.load_viz_notebook_data(year,\n", - " month,\n", - " program,\n", - " study_type,\n", - " dic_re,\n", - " dic_pur=dic_pur,\n", - " include_test_users=include_test_users)\n", - "# CASE 1 of https://github.com/e-mission/em-public-dashboard/issues/69#issuecomment-1256835867 \n", - "expanded_ct = scaffolding.add_energy_impact(expanded_ct, df_ei, dic_fuel) if len(expanded_ct) > 0 else expanded_ct" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "625872b0", - "metadata": {}, - "outputs": [], - "source": [ - "if 'mode_confirm' in expanded_ct.columns:\n", - " mode_of_interest_df = expanded_ct.query(f\"mode_confirm == '{mode_of_interest}'\")\n", - " debug_df.loc[f\"{mode_of_interest}_trips\"] = len(mode_of_interest_df)\n", - " debug_df.loc[f\"{mode_of_interest}_trips_with_replaced_mode\"] = scaffolding.trip_label_count(\"Replaced_mode\", mode_of_interest_df)" - ] - }, - { - "cell_type": "markdown", - "id": "caring-aruba", - "metadata": {}, - "source": [ - "## Energy Impacts Relative to Specific Mode" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "blessed-leader", - "metadata": {}, - "outputs": [], - "source": [ - "# CASE 2 of https://github.com/e-mission/em-public-dashboard/issues/69#issuecomment-1256835867\n", - "data_eb = expanded_ct.query(f\"mode_confirm == '{mode_of_interest}'\") if \"mode_confirm\" in expanded_ct.columns else expanded_ct" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "seeing-court", - "metadata": {}, - "outputs": [], - "source": [ - "quality_text_mode = scaffolding.get_quality_text(expanded_ct, data_eb, mode_of_interest)" - ] - }, - { - "cell_type": "markdown", - "id": "advanced-complexity", - "metadata": {}, - "source": [ - "### Sketch of energy impact by trips for specified mode" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "dense-programmer", - "metadata": { - "scrolled": false - }, - "outputs": [], - "source": [ - "# ebei : ebike energy impact\n", - "plot_title_no_quality=f\"Sketch of Energy Impact of {mode_of_interest} trips\"\n", - "file_name =f'sketch_energy_impact_{mode_of_interest}%s' % file_suffix\n", - " \n", - "try:\n", - " ebei=data_eb.groupby('Replaced_mode').agg({'Energy_Impact(kWH)': ['sum', 'mean']},)\n", - " ebei.columns = ['Sketch of Total Energy_Impact(kWH)', 'Sketch of Average Energy_Impact(kWH)']\n", - " ebei= ebei.reset_index()\n", - " ebei = ebei.sort_values(by=['Sketch of Total Energy_Impact(kWH)'], ascending=False)\n", - " ebei['boolean'] = ebei['Sketch of Total Energy_Impact(kWH)'] > 0\n", - " net_energy_saved = round(sum(ebei['Sketch of Total Energy_Impact(kWH)']), 2)\n", - "\n", - " x = ebei['Sketch of Total Energy_Impact(kWH)']\n", - " y = ebei['Replaced_mode']\n", - " color =ebei['boolean']\n", - "\n", - " plot_title= plot_title_no_quality+f\"\\n Contribution by replaced mode towards a total of {net_energy_saved}(kWH)\\n\"+quality_text\n", - " energy_impact(x,y,color,plot_title,file_name)\n", - " alt_text = store_alt_text_bar(pd.DataFrame(x.values,y), file_name, plot_title)\n", - "except:\n", - " generate_missing_plot(plot_title_no_quality,debug_df,file_name)\n", - " alt_text = store_alt_text_missing(debug_df, file_name, plot_title_no_quality)" - ] - }, - { - "cell_type": "markdown", - "id": "strategic-sheet", - "metadata": {}, - "source": [ - "### Sketch of CO2 emissions impact by trips of specified mode" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "animated-place", - "metadata": {}, - "outputs": [], - "source": [ - "plot_title_no_quality=f\"Sketch of Total Pounds of CO2 Emissions of {mode_of_interest} trips\"\n", - "file_name =f'sketch_CO2impact_{mode_of_interest}%s' % file_suffix\n", - "\n", - "try:\n", - " ebco2=data_eb.groupby('Replaced_mode').agg({'CO2_Impact(lb)': ['sum', 'mean']},)\n", - " ebco2.columns = ['total_lb_CO2_emissions', 'average_lb_CO2_emission']\n", - " ebco2 = ebco2.reset_index()\n", - " ebco2 = ebco2.sort_values(by=['total_lb_CO2_emissions'], ascending=False)\n", - " ebco2['boolean'] = ebco2['total_lb_CO2_emissions'] > 0\n", - " net_CO2_emissions = round(sum(ebco2['total_lb_CO2_emissions']), 2)\n", - "\n", - " x = ebco2['total_lb_CO2_emissions']\n", - " y = ebco2['Replaced_mode']\n", - " color = ebco2['boolean']\n", - "\n", - " plot_title= plot_title_no_quality+f\"\\n Contribution by replaced mode towards a total of {net_CO2_emissions}(lb CO2 Emissions )\\n\"+quality_text\n", - " CO2_impact(x,y,color,plot_title,file_name)\n", - " alt_text = store_alt_text_bar(pd.DataFrame(x.values,y), file_name, plot_title)\n", - "except:\n", - " generate_missing_plot(plot_title_no_quality,debug_df,file_name)\n", - " alt_text = store_alt_text_missing(debug_df, file_name, plot_title_no_quality)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.12" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/viz_scripts/generic_metrics.ipynb b/viz_scripts/generic_metrics.ipynb deleted file mode 100644 index b59a6e4..0000000 --- a/viz_scripts/generic_metrics.ipynb +++ /dev/null @@ -1,400 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "academic-context", - "metadata": {}, - "source": [ - "## Generate Static Graphs" - ] - }, - { - "cell_type": "markdown", - "id": "medium-siemens", - "metadata": {}, - "source": [ - "These are the input parameters for the notebook. They will be automatically changed when the scripts to generate monthly statistics are run. You can modify them manually to generate multiple plots locally as well.\n", - "\n", - "Pass in `None` to remove the filters and plot all data. This is not recommended for production settings, but might be useful for reports based on data snapshots." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "narrative-hunter", - "metadata": {}, - "outputs": [], - "source": [ - "year = 2020\n", - "month = 11\n", - "program = \"default\"\n", - "study_type = \"study\"\n", - "mode_of_interest = None\n", - "include_test_users = False" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "activated-portugal", - "metadata": {}, - "outputs": [], - "source": [ - "from collections import defaultdict\n", - "\n", - "import numpy as np\n", - "import pandas as pd\n", - "\n", - "from plots import *\n", - "import scaffolding\n", - "\n", - "sns.set_style(\"whitegrid\")\n", - "sns.set()\n", - "%matplotlib inline" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "alternative-voltage", - "metadata": {}, - "outputs": [], - "source": [ - "# Loading mapping dictionaries from mapping_dictionaries notebook\n", - "%store -r dic_re\n", - "%store -r dic_pur\n", - "\n", - "# convert a dictionary to a defaultdict\n", - "dic_re = defaultdict(lambda: 'Other',dic_re)\n", - "dic_pur = defaultdict(lambda: 'Other',dic_pur)" - ] - }, - { - "cell_type": "markdown", - "id": "intellectual-columbus", - "metadata": {}, - "source": [ - "## Collect Data From Database" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "organic-pitch", - "metadata": {}, - "outputs": [], - "source": [ - "expanded_ct, file_suffix, quality_text, debug_df = scaffolding.load_viz_notebook_data(year,\n", - " month,\n", - " program,\n", - " study_type,\n", - " dic_re,\n", - " dic_pur=dic_pur,\n", - " include_test_users=include_test_users)" - ] - }, - { - "cell_type": "markdown", - "id": "modified-skiing", - "metadata": {}, - "source": [ - "## Generic Metrics" - ] - }, - { - "cell_type": "markdown", - "id": "distributed-peace", - "metadata": {}, - "source": [ - "### Distribution of Mode_confirm attribute" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "tracked-serbia", - "metadata": { - "scrolled": false - }, - "outputs": [], - "source": [ - "file_name='ntrips_mode_confirm%s' % file_suffix\n", - "plot_title_no_quality= \"Number of trips for each mode (selected by users)\"\n", - "try:\n", - " labels_mc = expanded_ct['Mode_confirm'].value_counts(dropna=True).keys().tolist()\n", - " values_mc = expanded_ct['Mode_confirm'].value_counts(dropna=True).tolist() \n", - " plot_title = plot_title_no_quality+\"\\n\"+quality_text\n", - " pie_chart_mode(plot_title,labels_mc,values_mc,file_name)\n", - " alt_text = store_alt_text_pie(pd.DataFrame(values_mc, labels_mc), file_name, plot_title)\n", - " print(expanded_ct['Mode_confirm'].value_counts(dropna=True))\n", - "except:\n", - " generate_missing_plot(plot_title_no_quality,debug_df,file_name)\n", - " alt_text = store_alt_text_missing(debug_df, file_name, plot_title_no_quality)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "about-seafood", - "metadata": { - "scrolled": false - }, - "outputs": [], - "source": [ - "plot_title_no_quality= \"Number of commute trips for each mode (selected by users)\"\n", - "file_name= 'ntrips_commute_mode_confirm%s' % file_suffix\n", - "\n", - "try:\n", - " labels_mc = expanded_ct.query(\"Trip_purpose == 'Work'\").Mode_confirm.value_counts(dropna=True).keys().tolist()\n", - " values_mc = expanded_ct.query(\"Trip_purpose == 'Work'\").Mode_confirm.value_counts(dropna=True).tolist()\n", - " commute_quality_text = scaffolding.get_quality_text(expanded_ct, expanded_ct.query(\"Trip_purpose == 'Work'\"), \"commute\", include_test_users)\n", - " plot_title= plot_title_no_quality+\"\\n\"+commute_quality_text\n", - " pie_chart_mode(plot_title,labels_mc,values_mc,file_name)\n", - " alt_text = store_alt_text_pie(pd.DataFrame(values_mc, labels_mc), file_name, plot_title)\n", - " print(expanded_ct.query(\"Trip_purpose == 'Work'\").Mode_confirm.value_counts(dropna=True))\n", - "except:\n", - " debug_df.loc[\"Commute_trips\"] = len(expanded_ct.query(\"Trip_purpose == 'Work'\")) if \"Trip_purpose\" in expanded_ct.columns else 0\n", - " generate_missing_plot(plot_title_no_quality,debug_df,file_name)\n", - " alt_text = store_alt_text_missing(debug_df, file_name, plot_title_no_quality)" - ] - }, - { - "cell_type": "markdown", - "id": "careful-spencer", - "metadata": {}, - "source": [ - "### Distribution of Trip_purpose attribute" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "conservative-september", - "metadata": { - "scrolled": false - }, - "outputs": [], - "source": [ - "plot_title_no_quality=\"Number of trips for each purposes (selected by users)\"\n", - "file_name= 'ntrips_purpose%s' % file_suffix\n", - "\n", - "try:\n", - " labels_tp = expanded_ct['Trip_purpose'].value_counts(dropna=True).keys().tolist()\n", - " values_tp = expanded_ct['Trip_purpose'].value_counts(dropna=True).tolist()\n", - " plot_title= plot_title_no_quality+\"\\n\"+quality_text\n", - " pie_chart_purpose(plot_title,labels_tp,values_tp,file_name)\n", - " alt_text = store_alt_text_pie(pd.DataFrame(values_tp, labels_tp), file_name, plot_title)\n", - " print(expanded_ct['Trip_purpose'].value_counts(dropna=True))\n", - "except:\n", - " generate_missing_plot(plot_title_no_quality,debug_df,file_name)\n", - " alt_text = store_alt_text_missing(debug_df, file_name, plot_title_no_quality)" - ] - }, - { - "cell_type": "markdown", - "id": "crucial-keyboard", - "metadata": {}, - "source": [ - "### Mode choice for trips under 10 miles" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "identified-replica", - "metadata": { - "scrolled": false - }, - "outputs": [], - "source": [ - "plot_title_no_quality=\"Mode confirmations for trips under 10 Miles\"\n", - "file_name ='ntrips_under10miles_mode_confirm%s' % file_suffix\n", - "try:\n", - " labels_d10 = expanded_ct.loc[(expanded_ct['distance_miles'] <= 10)].Mode_confirm.value_counts(dropna=True).keys().tolist()\n", - " values_d10 = expanded_ct.loc[(expanded_ct['distance_miles'] <= 10)].Mode_confirm.value_counts(dropna=True).tolist()\n", - " d10_quality_text = scaffolding.get_quality_text(expanded_ct, expanded_ct[expanded_ct['distance_miles'] <= 10], \"< 10 mile\", include_test_users)\n", - " plot_title= plot_title_no_quality+\"\\n\"+d10_quality_text\n", - " pie_chart_mode(plot_title,labels_d10,values_d10,file_name)\n", - " alt_text = store_alt_text_pie(pd.DataFrame(values_d10, labels_d10), file_name, plot_title)\n", - " print(expanded_ct.loc[(expanded_ct['distance_miles'] <= 10)].Mode_confirm.value_counts(dropna=True))\n", - "except:\n", - " d10_df = expanded_ct.query(\"distance_miles <= 10\") if \"distance_miles\" in expanded_ct.columns else expanded_ct\n", - " debug_df.loc[\"Trips_less_than_10_miles\"] = scaffolding.trip_label_count(\"Mode_confirm\", d10_df)\n", - " generate_missing_plot(plot_title_no_quality,debug_df,file_name)\n", - " alt_text = store_alt_text_missing(debug_df, file_name, plot_title_no_quality)" - ] - }, - { - "cell_type": "markdown", - "id": "dominant-company", - "metadata": {}, - "source": [ - "### Miles per chosen transport mode" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "satisfied-sharing", - "metadata": { - "scrolled": false - }, - "outputs": [], - "source": [ - "plot_title_no_quality=\"Miles for each mode (selected by users)\"\n", - "file_name ='miles_mode_confirm%s' % file_suffix\n", - "\n", - "try:\n", - " miles = expanded_ct.groupby('Mode_confirm').agg({'distance_miles': ['sum', 'count' , 'mean']})\n", - " miles.columns = ['Total (miles)', 'Count', 'Average (miles)']\n", - " miles = miles.reset_index()\n", - " miles =miles.sort_values(by=['Total (miles)'], ascending=False)\n", - "\n", - " #data\n", - " miles_dict = dict(zip(miles['Mode_confirm'], miles['Total (miles)']))\n", - "\n", - " labels_m = []\n", - " values_m = []\n", - "\n", - " for x, y in miles_dict.items():\n", - " labels_m.append(x)\n", - " values_m.append(y)\n", - " \n", - " plot_title=\"Miles for each mode (selected by users)\\n%s\" % quality_text\n", - "\n", - " pie_chart_mode(plot_title,labels_m,values_m,file_name)\n", - " alt_text = store_alt_text_pie(pd.DataFrame(values_m, labels_m), file_name, plot_title)\n", - " print(miles)\n", - "except:\n", - " generate_missing_plot(plot_title_no_quality,debug_df,file_name)\n", - " alt_text = store_alt_text_missing(debug_df, file_name, plot_title_no_quality) " - ] - }, - { - "cell_type": "markdown", - "id": "demanding-franklin", - "metadata": {}, - "source": [ - "### Average miles per transport mode selected (Mode_confirm)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "patent-cliff", - "metadata": {}, - "outputs": [], - "source": [ - "plot_title_no_quality=\" Average Miles for each mode with > 3 entries\"\n", - "file_name ='average_miles_mode_confirm%s' % file_suffix\n", - "\n", - "try:\n", - " data = miles.drop((miles.query(\"Count < 3\").index)).sort_values(by=['Average (miles)'], ascending=False)\n", - " x='Mode_confirm'\n", - " y='Average (miles)'\n", - " \n", - " plot_title= plot_title_no_quality+\"\\n\"+quality_text\n", - "\n", - " barplot_mode(data,x,y,plot_title,file_name)\n", - " alt_text = store_alt_text_bar(pd.DataFrame(data['Average (miles)'].values, data['Mode_confirm']), file_name, plot_title)\n", - "except:\n", - " generate_missing_plot(plot_title_no_quality,debug_df,file_name)\n", - " alt_text = store_alt_text_missing(debug_df, file_name, plot_title_no_quality) " - ] - }, - { - "cell_type": "markdown", - "id": "sound-bradley", - "metadata": {}, - "source": [ - "### Number of trips by day¶" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "alleged-subsection", - "metadata": {}, - "outputs": [], - "source": [ - "plot_title_no_quality=\"Number of trips by day\"\n", - "file_name ='ntrips_per_day%s' % file_suffix\n", - "\n", - "try:\n", - " fq_days = expanded_ct.groupby(['start_local_dt_day']).agg({'start_local_dt_day': ['sum', 'count']})\n", - " fq_days = fq_days.reset_index()\n", - " fq_days.columns = ['Day of the Month', 'Total', 'Number of Trips']\n", - "\n", - " data = fq_days\n", - " x = 'Day of the Month'\n", - " y = 'Number of Trips'\n", - " \n", - " plot_title= plot_title_no_quality+\"\\n\"+quality_text\n", - "\n", - " barplot_day(data,x,y,plot_title,file_name)\n", - " alt_text = store_alt_text_bar(pd.DataFrame(data['Number of Trips'].values, data['Day of the Month']), file_name, plot_title)\n", - "except:\n", - " generate_missing_plot(plot_title_no_quality,debug_df,file_name)\n", - " alt_text = store_alt_text_missing(debug_df, file_name, plot_title_no_quality) " - ] - }, - { - "cell_type": "markdown", - "id": "challenging-julian", - "metadata": {}, - "source": [ - "### Number of trips by day of week¶" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "hollywood-optimization", - "metadata": {}, - "outputs": [], - "source": [ - "plot_title_no_quality=\"Number of trips by weekday\"\n", - "file_name ='ntrips_per_weekday%s' % file_suffix\n", - "try:\n", - " fq_weekdays = expanded_ct.groupby(['start_local_dt_weekday']).agg({'start_local_dt_weekday': ['sum', 'count']})\n", - " fq_weekdays = fq_weekdays.reset_index()\n", - " fq_weekdays.columns = ['Weekday', 'Total', 'Number of Trips']\n", - " weekday_labels = [\"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\", \"Sun\"]\n", - " fq_weekdays[\"Weekday\"] = fq_weekdays.Weekday.apply(lambda x: weekday_labels[x])\n", - "\n", - " data = fq_weekdays\n", - " x = 'Weekday'\n", - " y = 'Number of Trips'\n", - "\n", - " plot_title= plot_title_no_quality+\"\\n\"+quality_text\n", - " \n", - " barplot_day(data,x,y,plot_title,file_name)\n", - " alt_text = store_alt_text_bar(pd.DataFrame(data['Number of Trips'].values, data['Weekday']), file_name, plot_title)\n", - "except:\n", - " generate_missing_plot(plot_title_no_quality,debug_df,file_name)\n", - " alt_text = store_alt_text_missing(debug_df, file_name, plot_title_no_quality) " - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.12" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/viz_scripts/generic_timeseries.ipynb b/viz_scripts/generic_timeseries.ipynb deleted file mode 100644 index ca012da..0000000 --- a/viz_scripts/generic_timeseries.ipynb +++ /dev/null @@ -1,372 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Generate Static Graphs" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "These are the input parameters for the notebook. They will be automatically changed when the scripts to generate monthly statistics are run. You can modify them manually to generate multiple plots locally as well.\n", - "\n", - "Pass in `None` to remove the filters and plot all data. This is not recommended for production settings, but might be useful for reports based on data snapshots." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "year = 2020\n", - "month = 11\n", - "program = \"default\"\n", - "study_type = \"study\"\n", - "mode_of_interest = None\n", - "include_test_users = False" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from collections import defaultdict\n", - "import datetime\n", - "\n", - "import numpy as np\n", - "import pandas as pd\n", - "\n", - "from plots import *\n", - "import scaffolding\n", - "\n", - "sns.set_style(\"whitegrid\")\n", - "sns.set()\n", - "%matplotlib inline" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Loading mapping dictionaries from mapping_dictionaries notebook\n", - "%store -r df_ei\n", - "%store -r dic_re\n", - "%store -r dic_fuel\n", - "\n", - "# convert a dictionary to a defaultdict\n", - "dic_re = defaultdict(lambda: 'Other',dic_re)\n", - "dic_fuel = defaultdict(lambda: 'Other',dic_fuel)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Collect Data From Database" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "scrolled": false - }, - "outputs": [], - "source": [ - "expanded_ct, file_suffix, quality_text, debug_df = scaffolding.load_viz_notebook_data(year,\n", - " month,\n", - " program,\n", - " study_type,\n", - " dic_re,\n", - " include_test_users=include_test_users)\n", - "expanded_ct = scaffolding.add_energy_labels(expanded_ct, df_ei, dic_fuel) if \"mode_confirm\" in expanded_ct.columns else expanded_ct" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Data Preprocessing" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Get timestamp from known year/month/day aggregated to days\n", - "sel_cols_no_label_dep = ['user_id','start_local_dt_year','start_local_dt_month','start_local_dt_day','distance_miles']\n", - "sel_cols_with_label_dep = sel_cols_no_label_dep + ['Mode_confirm','Mode_confirm_EI(kWH)','Mode_confirm_lb_CO2']\n", - "if len(expanded_ct) == 0:\n", - " data = expanded_ct.copy()\n", - "elif \"Mode_confirm\" not in expanded_ct.columns:\n", - " data = expanded_ct[sel_cols_no_label_dep].copy()\n", - "else:\n", - " data = expanded_ct[sel_cols_with_label_dep].copy()\n", - " \n", - "if len(expanded_ct) > 0:\n", - " data.rename(columns={'start_local_dt_year':'year','start_local_dt_month':'month','start_local_dt_day':'day'}, inplace=True)\n", - " data['date_time'] = pd.to_datetime(data[['year','month','day']])\n", - " data = data.drop(columns=['year','month','day'])\n", - "\n", - " # Categorical type will include all days/modes in groupby even if there is no data for a particular tabulation\n", - " data.user_id = pd.Categorical(data.user_id)\n", - " data.date_time = pd.Categorical(data.date_time)\n", - " \n", - " if \"Mode_confirm\" in expanded_ct.columns:\n", - " data.Mode_confirm = pd.Categorical(data.Mode_confirm, ordered=True, categories=np.unique(list(dic_re.values())))\n", - "\n", - "data.head()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "if len(expanded_ct) > 0:\n", - " # Get the count of unique users that were active on each given date\n", - " active_users = pd.DataFrame(data.groupby(['date_time'], as_index=False)['user_id'].nunique())\n", - " active_users.rename(columns={'user_id':'active_users'}, inplace=True)\n", - "\n", - " if \"Mode_confirm\" in expanded_ct.columns:\n", - " # Count the number of trips for each confirmed mode\n", - " mode_counts = data.groupby(['user_id','date_time','Mode_confirm'], as_index=False).size()\n", - " mode_counts.rename(columns={'size':'trip_count'}, inplace=True)\n", - "\n", - " # Sum daily distance traveled for each mode\n", - " mode_distance = data.groupby(['user_id','date_time','Mode_confirm'], as_index=False)[['distance_miles']].sum()\n", - " mode_distance.rename(columns={'sum':'distance_miles'}, inplace=True)\n", - " mode_distance['distance_miles'] = mode_distance['distance_miles'].fillna(0)\n", - "\n", - " # Sum daily emissions for each user\n", - " emissions = data.groupby(['user_id','date_time'], as_index=False)[['Mode_confirm_lb_CO2', 'distance_miles']].sum()\n", - " emissions['Mode_confirm_lb_CO2'] = emissions['Mode_confirm_lb_CO2'].fillna(0)\n", - " emissions['distance_miles'] = emissions['Mode_confirm_lb_CO2'].fillna(0)\n", - "\n", - " # Sum daily energy for each user\n", - " energy = data.groupby(['user_id','date_time'], as_index=False)[['Mode_confirm_EI(kWH)', 'distance_miles']].sum()\n", - " energy['Mode_confirm_EI(kWH)'] = energy['Mode_confirm_EI(kWH)'].fillna(0)\n", - " energy['distance_miles'] = energy['Mode_confirm_EI(kWH)'].fillna(0)\n", - "\n", - " # Add 7-day rolling avg smoothing to better see trends\n", - " mode_counts['trip_count_smooth'] = mode_counts.groupby(['user_id','Mode_confirm'])['trip_count'].apply(lambda x: x.rolling(7,1).mean())\n", - " mode_distance['distance_miles_smooth'] = mode_distance.groupby(['user_id','Mode_confirm'])['distance_miles'].apply(lambda x: x.rolling(7,1).mean())\n", - " emissions['distance_miles_smooth'] = emissions.groupby(['user_id'])['distance_miles'].apply(lambda x: x.rolling(7,1).mean())\n", - " energy['distance_miles_smooth'] = energy.groupby(['user_id'])['distance_miles'].apply(lambda x: x.rolling(7,1).mean())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Generate Timeseries Plots" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Emissions per week" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "plot_title_no_quality = 'Net Daily Emissions (All Users, excluding air)'\n", - "file_name = \"ts_emissions_user%s\"%file_suffix\n", - "\n", - "try:\n", - " # Emissions per week across all users (net impact)\n", - " plot_data = emissions.groupby(['date_time'], as_index=False)['Mode_confirm_lb_CO2'].agg(['sum'])\n", - " plot_data = plot_data.merge(active_users, on='date_time')\n", - " plot_data['sum'] = plot_data['sum'] / plot_data['active_users']\n", - "\n", - " plot_title= plot_title_no_quality+\"\\n\"+quality_text\n", - " ylab = 'Emissions (lb CO2/day/user)'\n", - " timeseries_plot(plot_data['date_time'], plot_data['sum'], plot_title, ylab, file_name)\n", - " alt_text = store_alt_text_timeseries(plot_data, file_name, plot_title)\n", - "except:\n", - " generate_missing_plot(plot_title_no_quality,debug_df,file_name)\n", - " alt_text = store_alt_text_missing(debug_df, file_name, plot_title_no_quality)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Energy per week" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "plot_title_no_quality = 'Net Daily Energy (All Users, excluding air)'\n", - "file_name = \"ts_energy_user%s\"%file_suffix\n", - "\n", - "try:\n", - " # Energy per week across all users (net impact)\n", - " plot_data = energy.groupby(['date_time'], as_index=False)['Mode_confirm_EI(kWH)'].agg(['sum'])\n", - " plot_data = plot_data.merge(active_users, on='date_time')\n", - " plot_data['sum'] = plot_data['sum'] / plot_data['active_users']\n", - "\n", - " plot_title= plot_title_no_quality+\"\\n\"+quality_text\n", - " ylab = 'Energy (kWH/day/user)'\n", - " timeseries_plot(plot_data['date_time'], plot_data['sum'], plot_title, ylab, file_name)\n", - " alt_text = store_alt_text_timeseries(plot_data, file_name, plot_title)\n", - "except:\n", - " generate_missing_plot(plot_title_no_quality,debug_df,file_name)\n", - " alt_text = store_alt_text_missing(debug_df, file_name, plot_title_no_quality)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Emissions per mile per day" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "plot_title_no_quality = 'Average Daily Emission Rate (All Users, excluding air)'\n", - "file_name = \"ts_emissions_vmt%s\"%file_suffix\n", - "\n", - "try:\n", - " # Emissions per mile per day across all users (travel efficiency)\n", - " # Note that the energy plot will be identical to this one since scale factor is divided out\n", - " emissions['CO2_per_mile'] = emissions['Mode_confirm_lb_CO2'] / emissions['distance_miles_smooth']\n", - " emissions['CO2_per_mile'] = emissions['CO2_per_mile'].fillna(0)\n", - " plot_data = emissions.groupby(['date_time'])['CO2_per_mile'].agg(['mean']).reset_index()\n", - "\n", - " plot_title= plot_title_no_quality+\"\\n\"+quality_text\n", - " ylab = 'Emissions (lb CO2/mile/day)'\n", - " timeseries_plot(plot_data['date_time'], plot_data['mean'], plot_title, ylab, file_name)\n", - " alt_text = store_alt_text_timeseries(plot_data, file_name, plot_title)\n", - "except:\n", - " generate_missing_plot(plot_title_no_quality,debug_df,file_name)\n", - " alt_text = store_alt_text_missing(debug_df, file_name, plot_title_no_quality) " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Number of active users" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "plot_title_no_quality = 'Number of Active Users'\n", - "file_name = \"ts_users%s\"%file_suffix\n", - "\n", - "try:\n", - " # Plot of active users\n", - " plot_data = active_users\n", - "\n", - " plot_title= plot_title_no_quality+\"\\n\"+quality_text\n", - " ylab = 'Unique IDs'\n", - " timeseries_plot(plot_data['date_time'], plot_data['active_users'], plot_title, ylab, file_name)\n", - " alt_text = store_alt_text_timeseries(plot_data, file_name, plot_title)\n", - "except:\n", - " generate_missing_plot(plot_title_no_quality,debug_df,file_name)\n", - " alt_text = store_alt_text_missing(debug_df, file_name, plot_title_no_quality) " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Daily Mode share" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "plot_title_no_quality = 'Daily Aggregate Mode Share (excluding \"Other\" and \"Not a trip\"'\n", - "file_name = \"ts_all_modes%s\"%file_suffix\n", - "\n", - "try:\n", - " # Plot of mode share proportions across all users\n", - " # Consolidate modes\n", - " plot_data = mode_counts.replace('Bikeshare', 'Shared Micromobility')\n", - " plot_data = plot_data.replace('Scooter share', 'Shared Micromobility')\n", - " plot_data = plot_data.replace('Regular Bike', 'Personal Micromobility')\n", - " plot_data = plot_data.replace('Skate board', 'Personal Micromobility')\n", - " plot_data = plot_data.replace('Train', 'Transit')\n", - " plot_data = plot_data.replace('Free Shuttle', 'Transit')\n", - " plot_data = plot_data.replace('Bus', 'Transit')\n", - " plot_data = plot_data.replace('Walk', 'Walk')\n", - " plot_data = plot_data.replace('Taxi/Uber/Lyft', 'Ridehail')\n", - " plot_data = plot_data.replace('Pilot ebike', 'E-Bike')\n", - "\n", - " plot_data = plot_data.groupby(['date_time','Mode_confirm'], as_index=False)['trip_count_smooth'].sum()\n", - " total_trips = plot_data.groupby(['date_time'], as_index=False).sum()\n", - " plot_data = plot_data.merge(total_trips, on='date_time')\n", - " plot_data['trip_proportion'] = plot_data['trip_count_smooth_x'] / plot_data['trip_count_smooth_y']\n", - "\n", - " # Re-establish categorical variable to not include Other and Non-trips\n", - " plot_data = plot_data[~plot_data['Mode_confirm'].isin(['Not a Trip','Other'])]\n", - " plot_data.Mode_confirm = pd.Categorical(plot_data.Mode_confirm, ordered=True, categories=np.unique(list(dic_re.values())))\n", - "\n", - " plot_title= plot_title_no_quality+\"\\n\"+quality_text\n", - " ylab = 'Proportion of All Trips'\n", - " legend_title = 'Confirmed Mode'\n", - " timeseries_multi_plot(plot_data, 'date_time','trip_proportion','Mode_confirm', plot_title, ylab, legend_title, file_name)\n", - " alt_text = store_alt_text_generic('multivariate timeseries', file_name, plot_title)\n", - "except:\n", - " generate_missing_plot(plot_title_no_quality,debug_df,file_name)\n", - " alt_text = store_alt_text_missing(debug_df, file_name, plot_title_no_quality) " - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.12" - } - }, - "nbformat": 4, - "nbformat_minor": 1 -} diff --git a/viz_scripts/mapping_dictionaries.ipynb b/viz_scripts/mapping_dictionaries.ipynb deleted file mode 100644 index aa6046d..0000000 --- a/viz_scripts/mapping_dictionaries.ipynb +++ /dev/null @@ -1,65 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "available-fusion", - "metadata": {}, - "outputs": [], - "source": [ - "import pandas as pd" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "obvious-chapter", - "metadata": {}, - "outputs": [], - "source": [ - "df_pur = pd.read_csv(r'auxiliary_files/purpose_labels.csv')\n", - "df_re = pd.read_csv(r'auxiliary_files/mode_labels.csv')\n", - "df_ei = pd.read_csv(r'auxiliary_files/energy_intensity.csv')\n", - "\n", - "#dictionaries:\n", - "dic_pur = dict(zip(df_pur['purpose_confirm'],df_pur['bin_purpose'])) # bin purpose\n", - "dic_re = dict(zip(df_re['replaced_mode'],df_re['mode_clean'])) # bin modes\n", - "dic_fuel = dict(zip(df_ei['mode'],df_ei['fuel']))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "younger-indication", - "metadata": {}, - "outputs": [], - "source": [ - "%store df_ei\n", - "%store dic_re\n", - "%store dic_pur\n", - "%store dic_fuel" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.12" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/viz_scripts/mode_specific_metrics.ipynb b/viz_scripts/mode_specific_metrics.ipynb deleted file mode 100644 index 4e9da95..0000000 --- a/viz_scripts/mode_specific_metrics.ipynb +++ /dev/null @@ -1,373 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "worldwide-portuguese", - "metadata": {}, - "source": [ - "## Generate Static Graphs" - ] - }, - { - "cell_type": "markdown", - "id": "alive-integration", - "metadata": {}, - "source": [ - "These are the input parameters for the notebook. They will be automatically changed when the scripts to generate monthly statistics are run. You can modify them manually to generate multiple plots locally as well.\n", - "\n", - "Pass in `None` to remove the filters and plot all data. This is not recommended for production settings, but might be useful for reports based on data snapshots." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "former-luther", - "metadata": {}, - "outputs": [], - "source": [ - "year = 2020\n", - "month = 11\n", - "program = \"default\"\n", - "study_type = \"program\"\n", - "mode_of_interest = \"e-bike\"\n", - "include_test_users = False" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "dated-compromise", - "metadata": {}, - "outputs": [], - "source": [ - "from collections import defaultdict\n", - "\n", - "import numpy as np\n", - "import pandas as pd\n", - "\n", - "from plots import *\n", - "import scaffolding\n", - "\n", - "sns.set_style(\"whitegrid\")\n", - "sns.set()\n", - "%matplotlib inline" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "assisted-mathematics", - "metadata": {}, - "outputs": [], - "source": [ - "# Do not run this notebook at all unless it is for a program; nbclient will run up through this cell\n", - "if study_type != \"program\":\n", - " ipython = get_ipython()\n", - " ipython._showtraceback = scaffolding.no_traceback_handler\n", - " raise Exception(\"The plots in this notebook are only relevant to programs\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cathedral-scanning", - "metadata": {}, - "outputs": [], - "source": [ - "# Loading mapping dictionaries from mapping_dictionaries notebook\n", - "%store -r dic_re\n", - "%store -r dic_pur\n", - "\n", - "# convert a dictionary to a defaultdict\n", - "dic_re = defaultdict(lambda: 'Other',dic_re)\n", - "dic_pur = defaultdict(lambda: 'Other',dic_pur)" - ] - }, - { - "cell_type": "markdown", - "id": "built-occupation", - "metadata": {}, - "source": [ - "## Collect Data From Database" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "empty-intensity", - "metadata": {}, - "outputs": [], - "source": [ - "expanded_ct, file_suffix, quality_text, debug_df = scaffolding.load_viz_notebook_data(year,\n", - " month,\n", - " program,\n", - " study_type,\n", - " dic_re,\n", - " dic_pur=dic_pur,\n", - " include_test_users=include_test_users)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "77eedae6", - "metadata": {}, - "outputs": [], - "source": [ - "if 'mode_confirm' in expanded_ct.columns:\n", - " mode_of_interest_df = expanded_ct.query(f\"mode_confirm == '{mode_of_interest}'\")\n", - " debug_df.loc[f\"{mode_of_interest}_trips\"] = len(mode_of_interest_df)\n", - " debug_df.loc[f\"{mode_of_interest}_trips_with_replaced_mode\"] = scaffolding.trip_label_count(\"Replaced_mode\", mode_of_interest_df)" - ] - }, - { - "cell_type": "markdown", - "id": "surgical-continuity", - "metadata": {}, - "source": [ - "## Metrics for Specific Mode" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "romance-green", - "metadata": {}, - "outputs": [], - "source": [ - "data_eb = expanded_ct.query(f\"mode_confirm == '{mode_of_interest}'\") if \"mode_confirm\" in expanded_ct.columns else expanded_ct" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "seeing-court", - "metadata": {}, - "outputs": [], - "source": [ - "quality_text = scaffolding.get_quality_text(expanded_ct, data_eb, mode_of_interest, include_test_users)" - ] - }, - { - "cell_type": "markdown", - "id": "loaded-expert", - "metadata": {}, - "source": [ - "### Trips by purpose for specified mode" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "respiratory-breach", - "metadata": { - "scrolled": false - }, - "outputs": [], - "source": [ - "plot_title_no_quality=f\"Number of trips for each purpose for {mode_of_interest} only\"\n", - "file_name= f'ntrips_{mode_of_interest}_purpose%s' % file_suffix\n", - "\n", - "try:\n", - " labels_tp = data_eb['Trip_purpose'].value_counts(dropna=True).keys().tolist()\n", - " values_tp = data_eb['Trip_purpose'].value_counts(dropna=True).tolist()\n", - " plot_title= plot_title_no_quality+\"\\n\"+quality_text\n", - " pie_chart_purpose(plot_title,labels_tp,values_tp,file_name)\n", - " alt_text = store_alt_text_pie(pd.DataFrame(values_tp, labels_tp), file_name, plot_title)\n", - "except:\n", - " generate_missing_plot(plot_title_no_quality,debug_df,file_name)\n", - " alt_text = store_alt_text_missing(debug_df, file_name, plot_title_no_quality)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "thermal-midnight", - "metadata": {}, - "outputs": [], - "source": [ - "plot_title_no_quality=f\"Number of trips for each replaced transport mode for {mode_of_interest} only\"\n", - "file_name =f'ntrips_{mode_of_interest}_replaced_mode%s' % file_suffix\n", - "\n", - "try:\n", - " labels_eb = data_eb.Replaced_mode.value_counts(dropna=True).keys().tolist()\n", - " values_eb = data_eb.Replaced_mode.value_counts(dropna=True).tolist()\n", - " plot_title= plot_title_no_quality+\"\\n\"+quality_text\n", - " pie_chart_mode(plot_title,labels_eb,values_eb,file_name)\n", - " alt_text = store_alt_text_pie(pd.DataFrame(values_eb, labels_eb), file_name, plot_title)\n", - "except:\n", - " generate_missing_plot(plot_title_no_quality,debug_df,file_name)\n", - " alt_text = store_alt_text_missing(debug_df, file_name, plot_title_no_quality)" - ] - }, - { - "cell_type": "markdown", - "id": "dependent-reservoir", - "metadata": {}, - "source": [ - "### Miles for each mode replaced by the specified mode" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "pointed-velvet", - "metadata": {}, - "outputs": [], - "source": [ - "plot_title_no_quality=f\"Distribution of Miles Replaced by {mode_of_interest}\"\n", - "file_name =f'miles_{mode_of_interest}_replaced_mode%s' % file_suffix\n", - "\n", - "try:\n", - " dg=data_eb.groupby('Replaced_mode').agg({'distance_miles': ['sum', 'count' , 'mean']},)\n", - " dg.columns = ['Total (miles)', 'Count' ,'Average (miles)']\n", - " dg = dg.reset_index()\n", - " dg = dg.sort_values(by=['Total (miles)'], ascending=False)\n", - "\n", - " dg_dict = dict(zip(dg['Replaced_mode'], dg['Total (miles)']))\n", - " labels_m = []\n", - " values_m = []\n", - "\n", - " for x, y in dg_dict.items():\n", - " labels_m.append(x)\n", - " values_m.append(y)\n", - "\n", - " plot_title= plot_title_no_quality+\"\\n\"+quality_text\n", - " pie_chart_mode(plot_title,labels_m,values_m,file_name)\n", - " alt_text = store_alt_text_pie(pd.DataFrame(values_m, labels_m), file_name, plot_title)\n", - " print(dg)\n", - "except:\n", - " generate_missing_plot(plot_title_no_quality,debug_df,file_name)\n", - " alt_text = store_alt_text_missing(debug_df, file_name, plot_title_no_quality)" - ] - }, - { - "cell_type": "markdown", - "id": "honest-dylan", - "metadata": {}, - "source": [ - "### Average miles per trip for specified mode" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "binary-program", - "metadata": {}, - "outputs": [], - "source": [ - "plot_title_no_quality=\"Average Miles for each replaced mode with > 3 entries\\n'Other' represents trips with a non-standard or missing replacement\"\n", - "file_name ='average_miles_replaced_mode%s' % file_suffix\n", - "\n", - "try:\n", - " data = dg.drop((dg.query(\"Count < 3\").index)).sort_values(by=['Average (miles)'], ascending=False)\n", - " x='Replaced_mode'\n", - " y='Average (miles)'\n", - " y2 = \"Count\"\n", - "\n", - " plot_title= plot_title_no_quality+\"\\n\"+quality_text\n", - " barplot_mode(data,x,y,plot_title,file_name)\n", - " alt_text = store_alt_text_bar(pd.DataFrame(data['Average (miles)'].values, data.Replaced_mode), file_name, plot_title)\n", - "except:\n", - " generate_missing_plot(plot_title_no_quality,debug_df,file_name)\n", - " alt_text = store_alt_text_missing(debug_df, file_name, plot_title_no_quality)" - ] - }, - { - "cell_type": "markdown", - "id": "committed-favorite", - "metadata": {}, - "source": [ - "### Number of trips by day for specified mode" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "limiting-handling", - "metadata": {}, - "outputs": [], - "source": [ - "plot_title_no_quality=f\"Number of {mode_of_interest} trips by day\"\n", - "file_name =f'ntrips_{mode_of_interest}_per_day%s' % file_suffix\n", - "\n", - "\n", - "try:\n", - " fq_days = data_eb.groupby(['start_local_dt_day']).agg({'start_local_dt_day': ['sum', 'count']})\n", - " fq_days = fq_days.reset_index()\n", - " fq_days.columns = ['Day of the Month', 'Total', 'Number of Trips']\n", - "\n", - " data = fq_days\n", - " x = 'Day of the Month'\n", - " y = 'Number of Trips'\n", - "\n", - " plot_title= plot_title_no_quality+\"\\n\"+quality_text\n", - " barplot_day(data,x,y,plot_title,file_name)\n", - " alt_text = store_alt_text_bar(pd.DataFrame(data['Number of Trips'].values, data['Day of the Month'].values), file_name, plot_title)\n", - "except:\n", - " generate_missing_plot(plot_title_no_quality,debug_df,file_name)\n", - " alt_text = store_alt_text_missing(debug_df, file_name, plot_title_no_quality)" - ] - }, - { - "cell_type": "markdown", - "id": "pediatric-cowboy", - "metadata": {}, - "source": [ - "### Number of trips by day of week¶" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "metropolitan-musical", - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "plot_title_no_quality=f\"Number of {mode_of_interest} trips by weekday\"\n", - "file_name =f'ntrips_{mode_of_interest}_per_weekday%s' % file_suffix\n", - "\n", - "try:\n", - " fq_weekdays = data_eb.groupby(['start_local_dt_weekday']).agg({'start_local_dt_weekday': ['sum', 'count']})\n", - " fq_weekdays = fq_weekdays.reset_index()\n", - " fq_weekdays.columns = ['Weekday', 'Total', 'Number of Trips']\n", - " weekday_labels = [\"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\", \"Sun\"]\n", - " fq_weekdays[\"Weekday\"] = fq_weekdays.Weekday.apply(lambda x: weekday_labels[x])\n", - "\n", - " data = fq_weekdays\n", - " x = 'Weekday'\n", - " y = 'Number of Trips'\n", - "\n", - " plot_title= plot_title_no_quality+\"\\n\"+quality_text\n", - " barplot_day(data,x,y,plot_title,file_name)\n", - " alt_text = store_alt_text_bar(pd.DataFrame(data['Number of Trips'].values, data['Weekday'].values), file_name, plot_title)\n", - "except:\n", - " generate_missing_plot(plot_title_no_quality,debug_df,file_name)\n", - " alt_text = store_alt_text_missing(debug_df, file_name, plot_title_no_quality)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.12" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/viz_scripts/mode_specific_timeseries.ipynb b/viz_scripts/mode_specific_timeseries.ipynb deleted file mode 100644 index a157bcc..0000000 --- a/viz_scripts/mode_specific_timeseries.ipynb +++ /dev/null @@ -1,491 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Generate Static Graphs" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "These are the input parameters for the notebook. They will be automatically changed when the scripts to generate monthly statistics are run. You can modify them manually to generate multiple plots locally as well.\n", - "\n", - "Pass in `None` to remove the filters and plot all data. This is not recommended for production settings, but might be useful for reports based on data snapshots." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "year = 2020\n", - "month = 11\n", - "program = \"default\"\n", - "study_type = \"program\"\n", - "mode_of_interest = \"e-bike\"\n", - "include_test_users = False" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from collections import defaultdict\n", - "import datetime\n", - "\n", - "import numpy as np\n", - "import pandas as pd\n", - "\n", - "from plots import *\n", - "import scaffolding\n", - "\n", - "sns.set_style(\"whitegrid\")\n", - "sns.set()\n", - "%matplotlib inline" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Do not run this notebook at all unless it is for a program; nbclient will run up through this cell\n", - "if study_type != \"program\":\n", - " ipython = get_ipython()\n", - " ipython._showtraceback = scaffolding.no_traceback_handler\n", - " raise Exception(\"The plots in this notebook are only relevant to programs\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Loading mapping dictionaries from mapping_dictionaries notebook\n", - "%store -r dic_re\n", - "%store -r dic_pur\n", - "\n", - "# convert a dictionary to a defaultdict\n", - "dic_re = defaultdict(lambda: 'Other',dic_re)\n", - "dic_pur = defaultdict(lambda: 'Other',dic_pur)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Collect Data From Database" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "expanded_ct, file_suffix, quality_text, debug_df = scaffolding.load_viz_notebook_data(year,\n", - " month,\n", - " program,\n", - " study_type,\n", - " dic_re,\n", - " dic_pur=dic_pur,\n", - " include_test_users=include_test_users)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "if 'mode_confirm' in expanded_ct.columns:\n", - " mode_of_interest_df = expanded_ct.query(f\"mode_confirm == '{mode_of_interest}'\")\n", - " debug_df.loc[f\"{mode_of_interest}_trips\"] = len(mode_of_interest_df)\n", - " debug_df.loc[f\"{mode_of_interest}_trips_with_replaced_mode\"] = scaffolding.trip_label_count(\"Replaced_mode\", mode_of_interest_df)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Data Preprocessing" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Get timestamp from known year/month/day aggregated to days\n", - "sel_cols_no_label_dep = ['user_id','start_local_dt_year','start_local_dt_month','start_local_dt_day','distance_miles']\n", - "sel_cols_with_label_dep = sel_cols_no_label_dep + ['mode_confirm']\n", - "if len(expanded_ct) == 0:\n", - " data = expanded_ct.copy()\n", - "elif \"mode_confirm\" not in expanded_ct.columns:\n", - " data = expanded_ct[sel_cols_no_label_dep].copy()\n", - "else:\n", - " data = expanded_ct[sel_cols_with_label_dep].copy()\n", - " \n", - "if len(expanded_ct) > 0:\n", - " data.rename(columns={'start_local_dt_year':'year','start_local_dt_month':'month','start_local_dt_day':'day'}, inplace=True)\n", - " data['date_time'] = pd.to_datetime(data[['year','month','day']])\n", - " data = data.drop(columns=['year','month','day'])\n", - "\n", - " # Categorical type will include all days/modes in groupby even if there is no data for a particular tabulation\n", - " data.user_id = pd.Categorical(data.user_id)\n", - " data.date_time = pd.Categorical(data.date_time)\n", - " \n", - "data.head()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "scrolled": false - }, - "outputs": [], - "source": [ - "if len(expanded_ct) > 0:\n", - " # Get the count of unique users that were active on each given date\n", - " active_users = pd.DataFrame(data.groupby(['date_time'], as_index=False)['user_id'].nunique())\n", - " active_users.rename(columns={'user_id':'active_users'}, inplace=True)\n", - "\n", - " if \"mode_confirm\" in expanded_ct.columns:\n", - " # Count the number of trips for each confirmed mode\n", - " mode_counts = data.groupby(['user_id','date_time','mode_confirm'], as_index=False).size()\n", - " mode_counts.rename(columns={'size':'trip_count'}, inplace=True)\n", - "\n", - " # Sum daily distance traveled for each mode\n", - " mode_distance = data.groupby(['user_id','date_time','mode_confirm'], as_index=False)[['distance_miles']].sum()\n", - " mode_distance.rename(columns={'sum':'distance_miles'}, inplace=True)\n", - " mode_distance['distance_miles'] = mode_distance['distance_miles'].fillna(0)\n", - "\n", - " # Add 7-day rolling avg smoothing to better see trends\n", - " mode_counts['trip_count_smooth'] = mode_counts.groupby(['user_id','mode_confirm'])['trip_count'].apply(lambda x: x.rolling(7,1).mean())\n", - " mode_distance['distance_miles_smooth'] = mode_distance.groupby(['user_id','mode_confirm'])['distance_miles'].apply(lambda x: x.rolling(7,1).mean())\n", - " \n", - " # This is the mode specific part\n", - " mode_counts_interest = mode_counts[mode_counts['mode_confirm']==mode_of_interest].copy()\n", - " mode_distance_interest = mode_distance[mode_distance['mode_confirm']==mode_of_interest].copy()\n", - " \n", - " # Mapping new mode labels with dictionaries\n", - " mode_counts['Mode_confirm'] = mode_counts['mode_confirm'].map(dic_re)\n", - " mode_counts_interest['Mode_confirm'] = mode_counts_interest['mode_confirm'].map(dic_re)\n", - " mode_distance_interest['Mode_confirm'] = mode_distance_interest['mode_confirm'].map(dic_re)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "quality_text = scaffolding.get_quality_text(expanded_ct, mode_counts_interest, mode_of_interest, include_test_users)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Generate Timeseries Plots" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Daily trips for mode of interest" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Plot of total ebikeshare trips across all users\n", - "plot_title_no_quality = f'Daily {mode_of_interest} Trips'\n", - "file_name = f\"ts_{mode_of_interest}_share%s\"%file_suffix\n", - "\n", - "try:\n", - " if len(mode_counts_interest) == 0:\n", - " # force error generation so that we will go into the \"missing\" data code path\n", - " raise RuntimeError(f\"No {mode_of_interest} trips found\")\n", - " plot_data = mode_counts_interest.groupby(['date_time'])['trip_count_smooth'].agg(['sum']).reset_index()\n", - " \n", - " plot_title= plot_title_no_quality+\"\\n\"+quality_text\n", - " ylab = 'Trip Count'\n", - "\n", - " timeseries_plot(plot_data['date_time'], plot_data['sum'], plot_title, ylab, file_name)\n", - " alt_text = store_alt_text_timeseries(plot_data, file_name, plot_title)\n", - "except:\n", - " generate_missing_plot(plot_title_no_quality,debug_df,file_name)\n", - " alt_text = store_alt_text_missing(debug_df, file_name, plot_title_no_quality)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Daily mileage for mode of interest" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Plot of total ebikeshare mileage across all users\n", - "plot_title_no_quality = f'Daily {mode_of_interest} Mileage'\n", - "file_name = f\"ts_{mode_of_interest}_miles%s\"%file_suffix\n", - "\n", - "try:\n", - " if len(mode_counts_interest) == 0:\n", - " # force error generation so that we will go into the \"missing\" data code path\n", - " raise RuntimeError(f\"No {mode_of_interest} trips found\")\n", - "\n", - " plot_data = mode_distance_interest.groupby(['date_time'])['distance_miles'].agg(['sum']).reset_index()\n", - "\n", - " plot_title= plot_title_no_quality+\"\\n\"+quality_text\n", - " ylab = 'Miles'\n", - "\n", - " timeseries_plot(plot_data['date_time'], plot_data['sum'], plot_title, ylab, file_name)\n", - " alt_text = store_alt_text_timeseries(plot_data, file_name, plot_title)\n", - "except:\n", - " generate_missing_plot(plot_title_no_quality,debug_df,file_name)\n", - " alt_text = store_alt_text_missing(debug_df, file_name, plot_title_no_quality)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Daily mileage per user for mode of interest" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Plot of total ebikeshare mileage normalized by number of users\n", - "plot_title_no_quality = f'Daily {mode_of_interest} Mileage per Active User'\n", - "file_name = f\"ts_{mode_of_interest}_miles_user%s\"%file_suffix\n", - "\n", - "try:\n", - " if len(mode_counts_interest) == 0:\n", - " # force error generation so that we will go into the \"missing\" data code path\n", - " raise RuntimeError(f\"No {mode_of_interest} trips found\")\n", - "\n", - " plot_data = mode_distance_interest.groupby(['date_time'])['distance_miles'].agg(['sum']).reset_index()\n", - " plot_data = plot_data.merge(active_users, on='date_time')\n", - " plot_data['mileage_per_user'] = plot_data['sum'] / plot_data['active_users']\n", - "\n", - " plot_title= plot_title_no_quality+\"\\n\"+quality_text\n", - " ylab = 'miles/user'\n", - "\n", - " timeseries_plot(plot_data['date_time'], plot_data['mileage_per_user'], plot_title, ylab, file_name)\n", - " alt_text = store_alt_text_timeseries(plot_data, file_name, plot_title)\n", - "except:\n", - " generate_missing_plot(plot_title_no_quality,debug_df,file_name)\n", - " alt_text = store_alt_text_missing(debug_df, file_name, plot_title_no_quality)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Proportion of total daily mileage for mode of interest" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Plot of ebike mileage share proportion across all users\n", - "plot_title_no_quality = f'Daily {mode_of_interest} Mileage Proportion (All Users, excluding air)'\n", - "file_name = f\"ts_{mode_of_interest}_miles_proportion%s\"%file_suffix\n", - "\n", - "try:\n", - " if len(mode_counts_interest) == 0:\n", - " # force error generation so that we will go into the \"missing\" data code path\n", - " raise RuntimeError(f\"No {mode_of_interest} trips found\")\n", - "\n", - " plot_data = mode_distance_interest.groupby(['date_time'], as_index=False)['distance_miles_smooth'].sum()\n", - " total_miles = mode_distance.groupby(['date_time'], as_index=False)['distance_miles_smooth'].sum()\n", - " plot_data = plot_data.merge(total_miles, on=['date_time'])\n", - " plot_data['miles_proportion'] = plot_data['distance_miles_smooth_x'] / plot_data['distance_miles_smooth_y']\n", - "\n", - " plot_title= plot_title_no_quality+\"\\n\"+quality_text\n", - " ylab = 'Proportion of Daily Miles'\n", - "\n", - " timeseries_plot(plot_data['date_time'], plot_data['miles_proportion'], plot_title, ylab, file_name)\n", - " alt_text = store_alt_text_timeseries(plot_data, file_name, plot_title)\n", - "except:\n", - " generate_missing_plot(plot_title_no_quality,debug_df,file_name)\n", - " alt_text = store_alt_text_missing(debug_df, file_name, plot_title_no_quality)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Proportion of total daily trips for mode of interest" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Plot of ebike trip share proportion across all users\n", - "plot_title_no_quality = f'Daily {mode_of_interest} Trip Proportion (All Users, excluding air)'\n", - "file_name = f\"ts_{mode_of_interest}_trips_proportion%s\"%file_suffix\n", - "\n", - "try:\n", - " if len(mode_counts_interest) == 0:\n", - " # force error generation so that we will go into the \"missing\" data code path\n", - " raise RuntimeError(f\"No {mode_of_interest} trips found\")\n", - "\n", - " plot_data = mode_counts.groupby(['date_time','mode_confirm'], as_index=False)['trip_count_smooth'].sum()\n", - " total_trips = plot_data.groupby(['date_time'], as_index=False).sum()\n", - " plot_data = plot_data.merge(total_trips, on='date_time')\n", - " plot_data['trip_proportion'] = plot_data['trip_count_smooth_x'] / plot_data['trip_count_smooth_y']\n", - " plot_data = plot_data[plot_data['mode_confirm']==mode_of_interest]\n", - "\n", - " plot_title= plot_title_no_quality+\"\\n\"+quality_text\n", - " ylab = 'Proportion of Daily Trips'\n", - "\n", - " timeseries_plot(plot_data['date_time'], plot_data['trip_proportion'], plot_title, ylab, file_name)\n", - " alt_text = store_alt_text_timeseries(plot_data.drop(columns=['mode_confirm','trip_count_smooth_x','trip_count_smooth_y']), file_name, plot_title)\n", - "except:\n", - " generate_missing_plot(plot_title_no_quality,debug_df,file_name)\n", - " alt_text = store_alt_text_missing(debug_df, file_name, plot_title_no_quality)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Daily trip proportion for mode of interest with error bounds" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Plots the number of trips per user with error bars\n", - "# from the seaborn documentation:\n", - "# By default, the plot aggregates over multiple y values at each value of x and \n", - "# shows an estimate of the central tendency and a confidence interval for that estimate.\n", - "# In our case, we have multiple trip proportions (one per user) for each day\n", - "# so the band represents the variation of the number of trips and the thick line represents the mean/median (unsure which)\n", - "# but this still doesn't tell us which users have dropped their ridership\n", - "\n", - "plot_title_no_quality = f'Daily {mode_of_interest} Trip Proportion for Individual users (Running average and variation)'\n", - "file_name = f\"ts_{mode_of_interest}_trip_individual_variation%s\"%file_suffix\n", - "\n", - "try:\n", - " if len(mode_counts_interest) == 0:\n", - " # force error generation so that we will go into the \"missing\" data code path\n", - " raise RuntimeError(f\"No {mode_of_interest} trips found\")\n", - "\n", - " plot_data_mode = mode_counts_interest.groupby(['date_time', 'user_id'], as_index=False)[\"trip_count_smooth\"].sum()\n", - " plot_data = mode_counts.groupby(['date_time','user_id'], as_index=False)['trip_count_smooth'].sum()\n", - " plot_data = plot_data.merge(plot_data_mode, on=['date_time', 'user_id'])\n", - " plot_data['mode_trip_proportion'] = plot_data['trip_count_smooth_y'] / plot_data['trip_count_smooth_x']\n", - "\n", - " plot_title= plot_title_no_quality+\"\\n\"+quality_text\n", - " ylab = 'Proportion of Daily Trips'\n", - "\n", - " timeseries_plot(plot_data['date_time'], plot_data['mode_trip_proportion'], plot_title, ylab, file_name)\n", - " alt_text = store_alt_text_timeseries(plot_data.drop(columns=['user_id','trip_count_smooth_x','trip_count_smooth_y']), file_name, plot_title)\n", - "except:\n", - " generate_missing_plot(plot_title_no_quality,debug_df,file_name)\n", - " alt_text = store_alt_text_missing(debug_df, file_name, plot_title_no_quality)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Daily mileage proportion for mode of interest with error bounds" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Plots the number of trips per user with error bars\n", - "# from the seaborn documentation:\n", - "# By default, the plot aggregates over multiple y values at each value of x and \n", - "# shows an estimate of the central tendency and a confidence interval for that estimate.\n", - "# In our case, we have multiple trip proportions (one per user) for each day\n", - "# so the band represents the variation of the number of trips and the thick line represents the mean/median (unsure which)\n", - "# but this still doesn't tell us which users have dropped their ridership\n", - "\n", - "plot_title = f'Daily {mode_of_interest} Mileage Proportion for Individual users (Running average and variation)'\n", - "file_name = f\"ts_{mode_of_interest}_mile_individual_variation%s\"%file_suffix\n", - "\n", - "try:\n", - " if len(mode_distance_interest) == 0:\n", - " # force error generation so that we will go into the \"missing\" data code path\n", - " raise RuntimeError(f\"No {mode_of_interest} trips found\")\n", - "\n", - " plot_data_mode = mode_distance_interest.groupby(['date_time', 'user_id'], as_index=False)[\"distance_miles_smooth\"].sum()\n", - " plot_data = mode_distance.groupby(['date_time','user_id'], as_index=False)['distance_miles_smooth'].sum()\n", - " plot_data = plot_data.merge(plot_data_mode, on=['date_time', 'user_id'])\n", - " plot_data['mode_miles_proportion'] = plot_data['distance_miles_smooth_y'] / plot_data['distance_miles_smooth_x']\n", - "\n", - " plot_title= plot_title_no_quality+\"\\n\"+quality_text\n", - " ylab = 'Proportion of Daily Miles'\n", - "\n", - " timeseries_plot(plot_data['date_time'], plot_data['mode_miles_proportion'], plot_title, ylab, file_name)\n", - " alt_text = store_alt_text_timeseries(plot_data.drop(columns=['user_id','distance_miles_smooth_x','distance_miles_smooth_y']), file_name, plot_title)\n", - "except:\n", - " generate_missing_plot(plot_title_no_quality,debug_df,file_name)\n", - " alt_text = store_alt_text_missing(debug_df, file_name, plot_title_no_quality)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.12" - } - }, - "nbformat": 4, - "nbformat_minor": 1 -} diff --git a/viz_scripts/plots.py b/viz_scripts/plots.py deleted file mode 100644 index fa88b9b..0000000 --- a/viz_scripts/plots.py +++ /dev/null @@ -1,437 +0,0 @@ -import pandas as pd -import numpy as np -import arrow -import itertools -import matplotlib.pyplot as plt -import seaborn as sns -from matplotlib.patches import Patch - -sns.set_style("whitegrid") -sns.set() -get_ipython().run_line_magic('matplotlib', 'inline') - -# Module for pretty-printing outputs (e.g. head) to help users -# understand what is going on -# However, this means that this module can only be used in an ipython notebook - -import IPython.display as disp - - -SAVE_DIR="/plots/" - - -def merge_small_entries(labels, values): - v2l_df = pd.DataFrame({"vals": values}, index=labels) - - # Calculate % for all the values - vs = v2l_df.vals.sum() - v2l_df["pct"] = v2l_df.vals.apply(lambda x: (x/vs) * 100) - disp.display(v2l_df) - - # Find small chunks to combine - small_chunk = v2l_df.where(lambda x: x.pct <= 2).dropna() - misc_count = small_chunk.sum() - - v2l_df = v2l_df.drop(small_chunk.index) - disp.display(v2l_df) - - # This part if a bit tricky - # We could have already had a non-zero other, and it could be small or large - if "Other" not in v2l_df.index: - # zero other will end up with misc_count - v2l_df.loc["Other"] = misc_count - elif "Other" in small_chunk.index: - # non-zero small other will already be in misc_count - v2l_df.loc["Other"] = misc_count - else: - # non-zero large other, will not already be in misc_count - v2l_df.loc["Other"] = v2l_df.loc["Other"] + misc_count - disp.display(v2l_df) - - return (v2l_df.index.to_list(),v2l_df.vals.to_list()) - -def pie_chart_mode(plot_title,labels,values,file_name): - all_labels= ['Gas Car, drove alone', - 'Bus', - 'Train', - 'Free Shuttle', - 'Taxi/Uber/Lyft', - 'Gas Car, with others', - 'Bikeshare', - 'Scooter share', - 'E-bike', - 'Walk', - 'Skate board', - 'Regular Bike', - 'Not a Trip', - 'No Travel', - 'Same Mode', - 'E-car, drove alone', - 'E-car, with others', - 'Air', - 'Other'] - - val2labeldf = pd.DataFrame({"labels": labels, "values": values}) - - colours = dict(zip(all_labels, plt.cm.tab20.colors[:len(all_labels)])) - fig, ax = plt.subplots(figsize=(10, 10), subplot_kw=dict(aspect="equal")) - - m_labels, m_values = merge_small_entries(labels, values) - - def func(pct, values): - total = sum(values) - absolute = int(round(pct*total/100.0)) - return "{:.1f}%\n({:d})".format(pct, absolute) if pct > 4 else'' - - wedges, texts, autotexts = ax.pie(m_values, - labels = m_labels, - colors=[colours[key] for key in labels], - pctdistance=0.75, - autopct= lambda pct: func(pct, values), - textprops={'size': 23}) - - ax.set_title(plot_title, size=25) - plt.text(-1.3,-1.3,f"Last updated {arrow.get()}", fontsize=10) - plt.setp(autotexts, **{'color':'white', 'weight':'bold', 'fontsize':20}) - plt.savefig(SAVE_DIR+file_name+".png", bbox_inches='tight') - plt.show() - -def pie_chart_purpose(plot_title,labels,values,file_name): - labels_trip= ['Work', - 'Home', - 'Meal', - 'Shopping', - 'Personal/Medical', - 'Recreation/Exercise', - 'Transit transfer', - 'Pick-up/Drop off', - 'Entertainment/Social', - 'Other', - 'School', - 'Religious', - 'No travel', - 'not_a_trip'] - - colours = dict(zip(labels_trip, plt.cm.tab20.colors[:len(labels_trip)])) - fig, ax = plt.subplots(figsize=(10, 10), subplot_kw=dict(aspect="equal")) - - m_labels, m_values = merge_small_entries(labels, values) - - def func(pct, values): - total = sum(values) - absolute = int(round(pct*total/100.0)) - return "{:.1f}%\n({:d})".format(pct, absolute) if pct > 3 else'' - - wedges, texts, autotexts = ax.pie(m_values, - labels = m_labels, - colors=[colours[key] for key in labels], - pctdistance=0.85, - autopct=lambda pct: func(pct, values), - textprops={'size': 23}) - - ax.set_title(plot_title, size=25) - plt.text(-1.3,-1.3,f"Last updated {arrow.get()}", fontsize=10) - plt.setp(autotexts, **{'color':'white', 'weight':'bold', 'fontsize':20}) - plt.savefig(SAVE_DIR+file_name+".png", bbox_inches='tight') - plt.show() - -def energy_impact(x,y,color,plot_title,file_name): - color = color.map({True: 'green', False: 'red'}) - objects = ('Energy Savings', 'Energy Loss') - - y_labels = y - plt.figure(figsize=(15, 8)) - width = 0.8 - ax = x.plot(kind='barh',width=width, color=color) - ax.set_title(plot_title, fontsize=18) - ax.set_xlabel('Energy_Impact(kWH)', fontsize=18) - ax.set_ylabel('Replaced Mode',fontsize=18) - ax.set_yticklabels(y_labels) - ax.xaxis.set_tick_params(labelsize=15) - ax.yaxis.set_tick_params(labelsize=15) - ax.relim() - ax.autoscale_view() - - rects = ax.patches - for rect in rects: - x_value = rect.get_width() - y_value = rect.get_y() + rect.get_height() / 2 - space = 5 - ha = 'left' - - if x_value < 0: - space *= -1 - ha = 'right' - - label = "{:.1f}".format(x_value) - - # Create annotation - plt.annotate( - label, - (x_value, y_value), - xytext=(space, 0), - textcoords="offset points", - va='center', - ha=ha, fontsize=12, color='black', fontweight='bold') - - # map names to colors - cmap = {True: 'green', False: 'red'} - patches = [Patch(color=v, label=k) for k, v in cmap.items()] - plt.text(0,-1.5,f"Last updated {arrow.get()}", fontsize=10) - plt.legend(labels=objects, handles=patches, loc='upper right', borderaxespad=0, fontsize=15, frameon=True) - plt.savefig(SAVE_DIR+file_name+".png", bbox_inches='tight') - -def barplot_mode(data,x,y,plot_title,file_name): - all_labels= ['Gas Car, drove alone', - 'Bus', - 'Train', - 'Free Shuttle', - 'Taxi/Uber/Lyft', - 'Gas Car, with others', - 'Bikeshare', - 'Scooter share', - 'E-bike', - 'Walk', - 'Skate board', - 'Regular Bike', - 'Not a Trip', - 'No Travel', - 'Same Mode', - 'E-car, drove alone', - 'E-car, with others', - 'Air', - 'Other'] - - colours = dict(zip(all_labels, plt.cm.tab20.colors[:len(all_labels)])) - sns.set(font_scale=1.5) - f = plt.subplots(figsize=(15, 6)) - sns.set(style='whitegrid') - ax = sns.barplot(x=x, y=y, palette=colours,data=data, ci=None) - plt.xlabel(x, fontsize=23) - plt.ylabel(y, fontsize=23) - plt.title(plot_title, fontsize=25) - # y should be based on the max range + the biggest label ("Gas Car, with others") - plt.text(0,-(data[y].max()/8 + 3.3),f"Last updated {arrow.get()}", fontsize=10) - plt.setp(plt.gca().get_xticklabels(), rotation=45, horizontalalignment='right') - plt.savefig(SAVE_DIR+file_name+".png", bbox_inches='tight') - -def barplot_mode2(data,x,y,y2,plot_title,file_name): - all_labels= ['Gas Car, drove alone', - 'Bus', - 'Train', - 'Free Shuttle', - 'Taxi/Uber/Lyft', - 'Gas Car, with others', - 'Bikeshare', - 'Scooter share', - 'E-bike', - 'Walk', - 'Skate board', - 'Regular Bike', - 'Not a Trip', - 'No Travel', - 'Same Mode', - 'E-car, drove alone', - 'E-car, with others', - 'Air', - 'Other'] - - colours = dict(zip(all_labels, plt.cm.tab20.colors[:len(all_labels)])) - sns.set(font_scale=1.5) - - fig, ax1 = plt.subplots(figsize=(15,6)) - #bar plot creation - ax1.set_title(plot_title, fontsize=16) - plt.text(0,-2,f"Last updated {arrow.get()}", fontsize=10) - ax1.set_xlabel(x, fontsize=16) - ax1.set_ylabel(y, fontsize=16) - ax1 = sns.barplot(x=x, y=y, data = data, palette=colours, ci=None) - ax1.grid(False) - - #specify we want to share the same x-axis - ax2 = ax1.twinx() - color = 'tab:red' - #line plot creation - ax2.set_ylabel('Count', fontsize=16) - ax2 = sns.pointplot(x=x, y=y2, data = data, sort=False, color=color) - ax2.grid(False) - plt.setp(ax2.get_xticklabels(), rotation=45, ha='right') - plt.setp(ax1.get_xticklabels(), rotation=45, ha='right') - plt.savefig(SAVE_DIR+file_name+".png", bbox_inches='tight') - -def barplot_day(data,x,y,plot_title,file_name): - sns.set(font_scale=1.5) - f = plt.subplots(figsize=(15, 6)) - sns.set(style='whitegrid') - ax = sns.barplot(x=x, y=y,data=data, ci=None, color='blue') - plt.xlabel(x, fontsize=16) - plt.ylabel(y, fontsize=16) - plt.title(plot_title, fontsize=16) - # heuristic where we take the max value and divide it by 8 to get the scale - # the 8 is heuristic based on experimentation with the CanBikeCO data - plt.text(0,-(data[y].max())/8,f"Last updated {arrow.get()}", fontsize=10) - plt.savefig(SAVE_DIR+file_name+".png", bbox_inches='tight') - -def CO2_impact(x,y,color,plot_title,file_name): - color = color.map({True: 'green', False: 'red'}) - objects = ('CO2 Reduction', 'CO2 Increase') - - y_labels = y - plt.figure(figsize=(15, 8)) - width = 0.8 - ax = x.plot(kind='barh',width=width, color=color) - ax.set_title(plot_title, fontsize=18) - ax.set_xlabel('CO2 Emissions (lb)', fontsize=18) - ax.set_ylabel('Replaced Mode',fontsize=18) - ax.set_yticklabels(y_labels) - ax.xaxis.set_tick_params(labelsize=15) - ax.yaxis.set_tick_params(labelsize=15) - ax.relim() - ax.autoscale_view() - - rects = ax.patches - for rect in rects: - x_value = rect.get_width() - y_value = rect.get_y() + rect.get_height() / 2 - space = 5 - ha = 'left' - - if x_value < 0: - space *= -1 - ha = 'right' - - label = "{:.1f}".format(x_value) - - # Create annotation - plt.annotate( - label, - (x_value, y_value), - xytext=(space, 0), - textcoords="offset points", - va='center', - ha=ha, fontsize=12, color='black', fontweight='bold') - - # map names to colors - cmap = {True: 'green', False: 'red'} - patches = [Patch(color=v, label=k) for k, v in cmap.items()] - plt.text(0,-1.5,f"Last updated {arrow.get()}", fontsize=10) - plt.legend(labels=objects, handles=patches, loc='upper right', borderaxespad=0, fontsize=15, frameon=True) - plt.savefig(SAVE_DIR+file_name+".png", bbox_inches='tight') - -def timeseries_plot(x,y,plot_title,ylab,file_name): - fig, ax = plt.subplots(figsize=(16,4)) - sns.lineplot(ax=ax, x=x, y=y).set(title=plot_title, xlabel='Date', ylabel=ylab) - plt.xticks(rotation=45) - plt.subplots_adjust(bottom=0.25) - ax.figure.savefig(SAVE_DIR+file_name+".png", bbox_inches='tight') - -def timeseries_multi_plot(data,x,y,hue,plot_title,ylab,legend_title,file_name): - fig, ax = plt.subplots(figsize=(16,4)) - sns.lineplot(ax=ax, data=data, x=x, y=y, hue=hue).set(title=plot_title, xlabel='Date', ylabel=ylab) - plt.xticks(rotation=45) - plt.subplots_adjust(bottom=0.25) - plt.legend(bbox_to_anchor=(1.02, 1), loc='best', borderaxespad=0, title=legend_title) - ax.figure.savefig(SAVE_DIR+file_name+".png", bbox_inches='tight') - -def access_alt_text(alt_text, chart_name): - """ Inputs: - alt_text = the text describing the chart - chart_name = the alt text file to save or update - """ - f = open(SAVE_DIR+chart_name+".txt",'w') - f.write(alt_text) - f.close() - return alt_text - -def store_alt_text_generic(chart_description, chart_name, var_name): - """ Inputs: - chart_description = what type of chart is it - chart_name = what to label chart by in the dictionary - var_name = the variable being analyzed across the chart - """ - # Fill out the alt text based on components of the chart and passed data - alt_text = f"{chart_description} chart of {var_name}." - alt_text = access_alt_text(alt_text, chart_name) - return alt_text - -def store_alt_text_bar(df, chart_name, var_name): - """ Inputs: - df = dataframe with index of item names, first column is counts - chart_name = what to label chart by in the dictionary - var_name = the variable being analyzed across pie slices - """ - # Fill out the alt text based on components of the chart and passed data - alt_text = f"Bar chart of {var_name}." - for i in range(0,len(df)): - alt_text += f" {df.index[i]} is {np.round(df.iloc[i,0], 1)}." - alt_text = access_alt_text(alt_text, chart_name) - return alt_text - -def store_alt_text_pie(df, chart_name, var_name): - """ Inputs: - df = dataframe with index of item names, first column is counts - chart_name = what to label chart by in the dictionary - var_name = the variable being analyzed across pie slices - """ - # Fill out the alt text based on components of the chart and passed data - alt_text = f"Pie chart of {var_name}." - for i in range(0,len(df)): - alt_text += f" {df.index[i]} is {np.round(df.iloc[i,0] / np.sum(df.iloc[:,0]) * 100, 1)}%." - alt_text = access_alt_text(alt_text, chart_name) - return alt_text - -def store_alt_text_timeseries(df, chart_name, var_name): - """ Inputs: - df = dataframe with first col of dates, second column is values - chart_name = what to label chart by in the dictionary - var_name = the variable being analyzed across pie slices - """ - # Fill out the alt text based on components of the chart and passed data - alt_text = f"Scatter chart of {var_name}." - arg_min = np.argmin(df.iloc[:,1]) - arg_max = np.argmax(df.iloc[:,1]) - alt_text += f" First minimum is {np.round(df.iloc[arg_min,1], 1)} on {df.iloc[arg_min,0]}. First maximum is {np.round(df.iloc[arg_max,1], 1)} on {df.iloc[arg_max,0]}." - alt_text = access_alt_text(alt_text, chart_name) - return alt_text - -def generate_missing_plot(plot_title,debug_df,file_name): - f, ax = plt.subplots(figsize=(10,10)) - - plt.title("Unable to generate plot\n"+plot_title+"\n Reason:", fontsize=25, color="red") - # Must keep the patch visible; otherwise the entire figure becomes transparent - # f.patch.set_visible(False) - ax.axis('off') - ax.axis('tight') - # ax = sns.barplot(x=debug_df['count'],y=debug_df.index, palette=sns.color_palette("Reds",n_colors=10)) - # ax.set_xlim(0, None) - # for i in ax.containers: - # ax.bar_label(i,) - the_table = plt.table(cellText=debug_df.values, - rowLabels=debug_df.index, - colLabels=debug_df.columns, - loc="center") - the_table.auto_set_font_size(False) - the_table.set_fontsize(20) - the_table.scale(1, 4) - cellDict = the_table.get_celld() - for i in range(1,len(debug_df)+1): - currCellTextStr = cellDict[(i,0)].get_text().get_text() - currCellTextFloat = float(currCellTextStr) - if np.isnan(currCellTextFloat): - cellDict[(i,0)].get_text().set_text("None") - if np.isnan(currCellTextFloat) or currCellTextFloat == 0: - cellDict[(i, 0)].get_text().set_color("red") - plt.savefig(SAVE_DIR+file_name+".png", bbox_inches='tight') - -def store_alt_text_missing(df, chart_name, var_name): - """ Inputs: - df = dataframe with index of debug information, first column is counts - chart_name = what to label chart by in the dictionary - var_name = the variable being analyzed across pie slices - """ - # Fill out the alt text based on components of the chart and passed data - alt_text = f"Unable to generate\nBar chart of {var_name}.\nReason:" - for i in range(0,len(df)): - alt_text += f" {df.index[i]} is {np.round(df.iloc[i,0], 1)}." - alt_text = access_alt_text(alt_text, chart_name) - return alt_text diff --git a/viz_scripts/run_from_host/generate_plots.sh b/viz_scripts/run_from_host/generate_plots.sh deleted file mode 100755 index 789fd1d..0000000 --- a/viz_scripts/run_from_host/generate_plots.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -docker exec em-public-dashboard_notebook-server_1 /bin/bash -c "/usr/src/app/saved-notebooks/docker/generate_plots.sh $*" diff --git a/viz_scripts/run_from_host/update_mappings.sh b/viz_scripts/run_from_host/update_mappings.sh deleted file mode 100755 index a2be9fc..0000000 --- a/viz_scripts/run_from_host/update_mappings.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -docker exec em-public-dashboard_notebook-server_1 /bin/bash -c "/usr/src/app/saved-notebooks/docker/update_mappings.sh $*" diff --git a/viz_scripts/scaffolding.py b/viz_scripts/scaffolding.py index 01b4158..40e0517 100644 --- a/viz_scripts/scaffolding.py +++ b/viz_scripts/scaffolding.py @@ -1,10 +1,17 @@ import pandas as pd import numpy as np import sys +from tqdm import tqdm +from shapely import Point import emission.storage.timeseries.abstract_timeseries as esta +import emission.storage.timeseries.builtin_timeseries as bts import emission.storage.timeseries.tcquery as esttc import emission.core.wrapper.localdate as ecwl +import emission.storage.decorations.trip_queries as esdt +from pandarallel import pandarallel +from multiprocessing import cpu_count + # Module for pretty-printing outputs (e.g. head) to help users # understand what is going on @@ -45,14 +52,14 @@ def get_participant_uuids(program, load_test_users): else: participant_list = all_users[np.logical_not(all_users.user_email.str.contains("_test_"))] participant_uuid_str = participant_list.uuid - disp.display(participant_list.user_email) + # disp.display(participant_list.user_email) return participant_uuid_str def load_all_confirmed_trips(tq): agg = esta.TimeSeries.get_aggregate_time_series() all_ct = agg.get_data_df("analysis/confirmed_trip", tq) print("Loaded all confirmed trips of length %s" % len(all_ct)) - disp.display(all_ct.head()) + # disp.display(all_ct.head()) return all_ct def load_all_participant_trips(program, tq, load_test_users): @@ -63,7 +70,7 @@ def load_all_participant_trips(program, tq, load_test_users): return all_ct participant_ct_df = all_ct[all_ct.user_id.isin(participant_list)] print("After filtering, found %s participant trips " % len(participant_ct_df)) - disp.display(participant_ct_df.head()) + # disp.display(participant_ct_df.head()) return participant_ct_df def filter_labeled_trips(mixed_trip_df): @@ -72,7 +79,7 @@ def filter_labeled_trips(mixed_trip_df): return mixed_trip_df labeled_ct = mixed_trip_df[mixed_trip_df.user_input != {}] print("After filtering, found %s labeled trips" % len(labeled_ct)) - disp.display(labeled_ct.head()) + # disp.display(labeled_ct.head()) return labeled_ct def expand_userinputs(labeled_ct): @@ -87,7 +94,7 @@ def expand_userinputs(labeled_ct): if len(labeled_ct) == 0: return labeled_ct label_only = pd.DataFrame(labeled_ct.user_input.to_list(), index=labeled_ct.index) - disp.display(label_only.head()) + # disp.display(label_only.head()) labels_per_trip = len(label_only.columns) print("Found %s columns of length %d" % (label_only.columns, labels_per_trip)) expanded_ct = pd.concat([labeled_ct, label_only], axis=1) @@ -99,9 +106,53 @@ def expand_userinputs(labeled_ct): assert len(expanded_ct.columns) == len(labeled_ct.columns) + labels_per_trip, \ ("Mismatch after expanding labels, expanded_ct.columns = %s != labeled_ct.columns %s" % (len(expanded_ct.columns), len(labeled_ct.columns))) - disp.display(expanded_ct.head()) + # disp.display(expanded_ct.head()) return expanded_ct +def get_section_durations(confirmed_trips: pd.DataFrame): + + # Initialize the parallel processing. + pandarallel.initialize(progress_bar=False) + + """ + Extract section-wise durations from trips for every trips. + + TOOO: There is a massive scope to improve performance here. + Since we apply row-wise and there are no inter-row dependencies, + Speeding this up is very possible. Exploring packages like parallel-pandas + is definitely something we could do. + """ + + # No worries, the inner function has access to these variables. + fallback_key = 'analysis/inferred_section' + primary_key = 'analysis/cleaned_section' + + def get_durations(user_id, trip_id): + + inferred_sections = esdt.get_sections_for_trip(key = fallback_key, + user_id = user_id, trip_id = trip_id) + + if inferred_sections and len(inferred_sections) > 0: + return [x.data.duration for x in inferred_sections] + + print("Falling back to confirmed trips...") + + cleaned_sections = esdt.get_sections_for_trip(key = primary_key, + user_id = user_id, trip_id = trip_id) + + if cleaned_sections and len(cleaned_sections) > 0: + return [x.data.duration for x in cleaned_sections] + + return [] + + confirmed_trips['section_durations'] = confirmed_trips.parallel_apply( + lambda x: get_durations(x.user_id, x.cleaned_trip), axis=1 + ) + + return confirmed_trips + + + # CASE 2 of https://github.com/e-mission/em-public-dashboard/issues/69#issuecomment-1256835867 unique_users = lambda df: len(df.user_id.unique()) if "user_id" in df.columns else 0 trip_label_count = lambda s, df: len(df[s].dropna()) if s in df.columns else 0 @@ -112,11 +163,15 @@ def load_viz_notebook_data(year, month, program, study_type, dic_re, dic_pur=Non dic_* = label mappings; if dic_pur is included it will be used to recode trip purpose Pipeline to load and process the data before use in visualization notebooks. + + year = None, month = None, program='prepilot', study_type='program', dict(), dict() + """ # Access database tq = get_time_query(year, month) participant_ct_df = load_all_participant_trips(program, tq, include_test_users) labeled_ct = filter_labeled_trips(participant_ct_df) + # labeled_ct = get_section_durations(labeled_ct) expanded_ct = expand_userinputs(labeled_ct) expanded_ct = data_quality_check(expanded_ct) diff --git a/viz_scripts/tests/TestGetMonthList.py b/viz_scripts/tests/TestGetMonthList.py deleted file mode 100644 index a9690e4..0000000 --- a/viz_scripts/tests/TestGetMonthList.py +++ /dev/null @@ -1,39 +0,0 @@ -import arrow -import unittest - -class TestGetMonthList(unittest.TestCase): - def test_same_month(self): - start_date = arrow.get(2020, 5, 1) - end_date = arrow.get(2020, 5, 1) - month_range = list(arrow.Arrow.range('month', start_date, end_date)) - self.assertEqual(len(month_range), 1) - self.assertEqual([m.year for m in month_range], [2020]) - self.assertEqual([m.month for m in month_range], [5]) - - def test_same_year(self): - start_date = arrow.get(2020, 5, 1) - end_date = arrow.get(2020, 10, 1) - month_range = list(arrow.Arrow.range('month', start_date, end_date)) - self.assertEqual([m.year for m in month_range], [2020] * 6) - self.assertEqual([m.month for m in month_range], list(range(5,11))) - - def test_less_than_twelve_months_spans_two_years(self): - start_date = arrow.get(2020, 7, 1) - end_date = arrow.get(2021, 5, 1) - month_range = list(arrow.Arrow.range('month', start_date, end_date)) - self.assertEqual([m.year for m in month_range[:6]], [2020] * 6) - self.assertEqual([m.year for m in month_range[6:]], [2021] * 5) - self.assertEqual([m.month for m in month_range[:6]], list(range(7, 13))) - self.assertEqual([m.month for m in month_range[6:]], list(range(1, 6))) - - def test_more_than_twelve_months_spans_two_years(self): - start_date = arrow.get(2020, 7, 1) - end_date = arrow.get(2021, 9, 1) - month_range = list(arrow.Arrow.range('month', start_date, end_date)) - self.assertEqual([m.year for m in month_range[:6]], [2020] * 6) - self.assertEqual([m.year for m in month_range[6:]], [2021] * 9) - self.assertEqual([m.month for m in month_range[:6]], list(range(7, 13))) - self.assertEqual([m.month for m in month_range[6:]], list(range(1, 10))) - -if __name__ == '__main__': - unittest.main() diff --git a/viz_scripts/variation_across_individuals.ipynb b/viz_scripts/variation_across_individuals.ipynb deleted file mode 100644 index 2e043f5..0000000 --- a/viz_scripts/variation_across_individuals.ipynb +++ /dev/null @@ -1,263 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "32b25c19", - "metadata": {}, - "outputs": [], - "source": [ - "# This is still exploratory analysis, so it is not converted to work with the cold start changes\n", - "# similar to the other notebooks" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "lyric-montgomery", - "metadata": {}, - "outputs": [], - "source": [ - "year = None\n", - "month = None\n", - "program = None" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "mobile-certificate", - "metadata": {}, - "outputs": [], - "source": [ - "import pandas as pd\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "import seaborn as sns\n", - "from collections import defaultdict\n", - "\n", - "sns.set_style(\"whitegrid\")\n", - "sns.set()\n", - "%matplotlib inline" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "existing-tanzania", - "metadata": {}, - "outputs": [], - "source": [ - "import scaffolding \n", - "from plots import *" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "unique-topic", - "metadata": {}, - "outputs": [], - "source": [ - "# Loading mapping dictionaries from mapping_dictionaries notebook\n", - "%store -r dic_ei\n", - "%store -r dic_re\n", - "%store -r dic_pur\n", - "\n", - "# convert a dictionary to a defaultdict\n", - "dic_pur = defaultdict(lambda: 'Other',dic_pur)\n", - "dic_re = defaultdict(lambda: 'Other',dic_re)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "legitimate-ethics", - "metadata": {}, - "outputs": [], - "source": [ - "tq = scaffolding.get_time_query(year, month)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "contrary-poverty", - "metadata": {}, - "outputs": [], - "source": [ - "participant_ct_df = scaffolding.load_all_participant_trips(program, tq)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "international-retention", - "metadata": {}, - "outputs": [], - "source": [ - "labeled_ct = scaffolding.filter_labeled_trips(participant_ct_df)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "dynamic-allowance", - "metadata": {}, - "outputs": [], - "source": [ - "expanded_ct = scaffolding.expand_userinputs(labeled_ct)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "micro-gathering", - "metadata": {}, - "outputs": [], - "source": [ - "expanded_ct.shape" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "sweet-muscle", - "metadata": {}, - "outputs": [], - "source": [ - "expanded_ct = scaffolding.data_quality_check(expanded_ct)\n", - "expanded_ct.shape" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "compact-reader", - "metadata": {}, - "outputs": [], - "source": [ - "## Mapping new labels with dictionaries\n", - "expanded_ct['Trip_purpose']= expanded_ct['purpose_confirm'].map(dic_pur)\n", - "expanded_ct['Mode_confirm']= expanded_ct['mode_confirm'].map(dic_re)\n", - "expanded_ct['Replaced_mode']= expanded_ct['replaced_mode'].map(dic_re)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "studied-oxide", - "metadata": {}, - "outputs": [], - "source": [ - "assert len(expanded_ct[(expanded_ct['Mode_confirm'] == 'Pilot ebike') & (expanded_ct[\"Replaced_mode\"] == \"Pilot ebike\")]) == 0" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "auburn-driving", - "metadata": {}, - "outputs": [], - "source": [ - "# Energy Impact Calculation\n", - "scaffolding.unit_conversions(expanded_ct)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "mental-compact", - "metadata": {}, - "outputs": [], - "source": [ - "# group by user\n", - "all_trip_user_count = participant_ct_df.groupby(\"user_id\")[\"user_id\"].agg([\"count\"])\n", - "all_trip_user_count" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "international-morrison", - "metadata": {}, - "outputs": [], - "source": [ - "# group by user\n", - "labeled_trip_user_count = expanded_ct.groupby(\"user_id\")[\"user_id\"].agg([\"count\"])\n", - "labeled_trip_user_count" - ] - }, - { - "cell_type": "markdown", - "id": "junior-scholar", - "metadata": {}, - "source": [ - "## eBike only" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "sticky-fiber", - "metadata": {}, - "outputs": [], - "source": [ - "data_eb = expanded_ct.query(\"Mode_confirm == 'Pilot ebike'\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "representative-scale", - "metadata": {}, - "outputs": [], - "source": [ - "eb_user_count = data_eb.groupby(\"user_id\")[\"user_id\"].count()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "liked-silver", - "metadata": {}, - "outputs": [], - "source": [ - "compare_df = pd.concat([eb_user_count, labeled_trip_user_count, all_trip_user_count], axis=1)\n", - "compare_df.columns = [\"ebike_trips\", \"labeled_trips\", \"all_trips\"]\n", - "compare_df[\"labeled Trip Pct\"] = (compare_df.labeled_trips * 100) / compare_df.all_trips\n", - "compare_df[\"ebike Trip Pct\"] = (compare_df.ebike_trips * 100) / compare_df.labeled_trips\n", - "compare_df" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "comic-audience", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.12" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -}