Spaces:
Sleeping
Sleeping
Upload 20 files
Browse filesadd folder for pages, and stuff
- APIs/README.md +117 -0
- APIs/example_api.ipynb +1 -0
- APIs/graphql_noauth.py +30 -0
- APIs/graphql_noauth_activity.py +84 -0
- APIs/openweather.md +14 -0
- pages/__pycache__/compare_locations.cpython-311.pyc +0 -0
- pages/__pycache__/compare_locations.cpython-39.pyc +0 -0
- pages/__pycache__/single_location.cpython-311.pyc +0 -0
- pages/__pycache__/single_location.cpython-39.pyc +0 -0
- pages/__pycache__/summary_metrics.cpython-311.pyc +0 -0
- pages/__pycache__/summary_metrics.cpython-39.pyc +0 -0
- pages/__pycache__/variable_selection.cpython-311.pyc +0 -0
- pages/__pycache__/variable_selection.cpython-39.pyc +0 -0
- pages/compare_locations.py +66 -0
- pages/single_location.py +25 -0
- pages/summary_metrics.py +66 -0
- pages/variable_selection.py +86 -0
- utils/__pycache__/weather_api.cpython-311.pyc +0 -0
- utils/__pycache__/weather_api.cpython-39.pyc +0 -0
- utils/weather_api.py +32 -0
APIs/README.md
ADDED
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Weather Dashboard for BYU Locations
|
2 |
+
|
3 |
+
## Overview
|
4 |
+
This Streamlit application provides weather insights for various BYU locations. Users can explore weather metrics, compare locations, and visualize key performance indicators (KPIs) and other weather variables. The app integrates with the Open-Meteo Weather API to fetch real-time and historical weather data.
|
5 |
+
|
6 |
+
### Features
|
7 |
+
1. **Single Location Weather:** View temperature data for a selected BYU location.
|
8 |
+
2. **Compare Locations & KPIs:** Compare weather metrics like temperature across BYU Idaho, BYU Hawaii, and BYU Provo.
|
9 |
+
3. **Variable Selection & Timezone:** Analyze multiple weather variables (e.g., Temperature, Wind Speed, Relative Humidity) with timezone options.
|
10 |
+
4. **Summary Metrics Across Cities:** View combined metrics and boxplots for weather variables across cities.
|
11 |
+
|
12 |
+
---
|
13 |
+
|
14 |
+
## Technologies Used
|
15 |
+
- **Streamlit**: Frontend interface for interactivity.
|
16 |
+
- **Polars**: High-performance dataframe library for data manipulation.
|
17 |
+
- **Requests**: Fetches weather data from the Open-Meteo API.
|
18 |
+
- **Matplotlib**: Visualizes weather trends and comparisons.
|
19 |
+
|
20 |
+
---
|
21 |
+
|
22 |
+
## Setup and Installation
|
23 |
+
Follow the steps below to get the app running locally:
|
24 |
+
|
25 |
+
### 1. Prerequisites
|
26 |
+
Ensure the following are installed:
|
27 |
+
- Python 3.8 or higher
|
28 |
+
- pip (Python package manager)
|
29 |
+
|
30 |
+
### 2. Clone the Repository
|
31 |
+
```bash
|
32 |
+
git clone <repository-link>
|
33 |
+
cd <repository-folder>
|
34 |
+
```
|
35 |
+
|
36 |
+
### 3. Install Dependencies
|
37 |
+
Run the following command to install required Python packages:
|
38 |
+
```bash
|
39 |
+
pip install -r requirements.txt
|
40 |
+
```
|
41 |
+
**Note**: Ensure `streamlit`, `polars`, `matplotlib`, and `requests` are included in your `requirements.txt`.
|
42 |
+
|
43 |
+
### 4. Project Directory Structure
|
44 |
+
The app assumes the following directory structure:
|
45 |
+
```
|
46 |
+
project-folder/
|
47 |
+
|└── APIs/
|
48 |
+
|└── pages/
|
49 |
+
| |└── single_location.py
|
50 |
+
| |└── compare_locations.py
|
51 |
+
| |└── variable_selection.py
|
52 |
+
| |└── summary_metrics.py
|
53 |
+
|└── screenshots/
|
54 |
+
|└── utils/
|
55 |
+
| |└── weather_api.py
|
56 |
+
|└── streamlit_app.py
|
57 |
+
|└── requirements.txt
|
58 |
+
|└── README.md
|
59 |
+
|└── docker-compose.yaml
|
60 |
+
|└── Dockerfile
|
61 |
+
```
|
62 |
+
|
63 |
+
### 5. Run the Application
|
64 |
+
Launch the app using Streamlit:
|
65 |
+
```bash
|
66 |
+
streamlit run streamlit_app.py
|
67 |
+
```
|
68 |
+
|
69 |
+
The app will open in your browser at `http://localhost:8501`.
|
70 |
+
|
71 |
+
---
|
72 |
+
|
73 |
+
## Usage Instructions
|
74 |
+
### Sidebar Navigation
|
75 |
+
The sidebar allows you to navigate between different app sections:
|
76 |
+
- **Single Location Weather**: Select a BYU location and view temperature data.
|
77 |
+
- **Compare Locations & KPIs**: Compare weather metrics across multiple locations.
|
78 |
+
- **Variable Selection & Timezone**: Analyze weather variables like temperature, wind speed, and humidity.
|
79 |
+
- **Summary Metrics Across Cities**: View combined metrics and visualizations.
|
80 |
+
|
81 |
+
### Input Parameters
|
82 |
+
- **Date Range**: Select a start and end date for fetching weather data.
|
83 |
+
- **Locations**: Choose from predefined BYU locations (Provo, Hawaii, Idaho).
|
84 |
+
- **Timezone Selection**: For variable analysis, choose between `America/Denver` or `Pacific/Honolulu`.
|
85 |
+
|
86 |
+
---
|
87 |
+
|
88 |
+
## API Details
|
89 |
+
- **Source**: [Open-Meteo API](https://open-meteo.com/)
|
90 |
+
- **Endpoint**: `https://api.open-meteo.com/v1/forecast`
|
91 |
+
- **Variables Supported**: Temperature, Wind Speed, Relative Humidity
|
92 |
+
|
93 |
+
---
|
94 |
+
|
95 |
+
## Screenshots
|
96 |
+
### Single Location Weather
|
97 |
+

|
98 |
+
|
99 |
+
### Compare Locations
|
100 |
+

|
101 |
+
|
102 |
+
### Variable Selection
|
103 |
+

|
104 |
+
|
105 |
+
### Summary Metrics
|
106 |
+

|
107 |
+
|
108 |
+
---
|
109 |
+
|
110 |
+
## Author
|
111 |
+
Christian Landaverde
|
112 |
+
|
113 |
+
---
|
114 |
+
|
115 |
+
## Acknowledgements
|
116 |
+
- Open-Meteo API for weather data.
|
117 |
+
- Streamlit for the interactive dashboard.
|
APIs/example_api.ipynb
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
{"cells":[{"cell_type":"code","source":["import pandas as pd\n\nimport requests\nimport json\nimport os\nimport datetime\nimport time\n\nimport pyspark.sql.functions as F"],"metadata":{"application/vnd.databricks.v1+cell":{"showTitle":false,"cellMetadata":{"rowLimit":10000,"byteLimit":2048000},"nuid":"846224e6-4452-43f8-9dad-a26705ff4f8c","inputWidgets":{},"title":""}},"outputs":[],"execution_count":0},{"cell_type":"markdown","source":["You will need to create a notebook with your keys in it in a notebook called `keys`. The next two lines provide examples of how to reference that notebook."],"metadata":{"application/vnd.databricks.v1+cell":{"showTitle":false,"cellMetadata":{},"nuid":"4d2b789e-2bd8-44f8-b24a-f03704d54eb9","inputWidgets":{},"title":""}}},{"cell_type":"code","source":["%run ./keys"],"metadata":{"application/vnd.databricks.v1+cell":{"showTitle":false,"cellMetadata":{"rowLimit":10000,"byteLimit":2048000},"nuid":"9fd037a7-d183-4e6a-bae7-0a657742bbbf","inputWidgets":{},"title":""}},"outputs":[],"execution_count":0},{"cell_type":"code","source":["%run ../sp23/keys"],"metadata":{"application/vnd.databricks.v1+cell":{"showTitle":false,"cellMetadata":{"rowLimit":10000,"byteLimit":2048000},"nuid":"bd674fa8-daa5-4317-9a57-6d5dc7e5a4c0","inputWidgets":{},"title":""}},"outputs":[{"output_type":"stream","output_type":"stream","name":"stdout","text":["ow_key is the variable\nd8a\n"]}],"execution_count":0},{"cell_type":"code","source":["# https://openweathermap.org/forecast16\napi_key = ow_key\n# https://api.openweathermap.org/data/2.5/forecast?lat=43.825386&lon=-111.792824&appid=d8aa64c57714e98a56057fa3bd9f478a&units=imperial&cnt=5\nlat = \"43.825386\"\nlon = \"-111.792824\"\nurl = \"https://api.openweathermap.org/data/2.5/forecast?lat=%s&lon=%s&appid=%s&units=imperial&cnt=5\" % (lat, lon, api_key)\nprint(url[0:85] + \"<YOURKEY>\" + url[117:])\n"],"metadata":{"application/vnd.databricks.v1+cell":{"showTitle":false,"cellMetadata":{"rowLimit":10000,"byteLimit":2048000},"nuid":"a7760181-3c8b-4183-bd40-9cd0a649fef2","inputWidgets":{},"title":""}},"outputs":[{"output_type":"stream","output_type":"stream","name":"stdout","text":["https://api.openweathermap.org/data/2.5/forecast?lat=43.825386&lon=-111.792824&appid=<YOURKEY>&units=imperial&cnt=5\n"]},{"output_type":"stream","output_type":"stream","name":"stdout","text":["ow_key is the variable\nd8a\n"]}],"execution_count":0},{"cell_type":"code","source":["# 200 is a good sign, #400 is a bad sign\nresponse = requests.get(url)\nresponse"],"metadata":{"application/vnd.databricks.v1+cell":{"showTitle":false,"cellMetadata":{"rowLimit":10000,"byteLimit":2048000},"nuid":"bac2a982-906a-446f-84df-aab0745a4780","inputWidgets":{},"title":""}},"outputs":[{"output_type":"stream","output_type":"stream","name":"stdout","text":["Out[51]: <Response [200]>"]}],"execution_count":0},{"cell_type":"code","source":["data = json.loads(response.text)\ndata"],"metadata":{"application/vnd.databricks.v1+cell":{"showTitle":false,"cellMetadata":{"rowLimit":10000,"byteLimit":2048000},"nuid":"4ec007c0-c8c7-4643-8436-33d09a8dd25c","inputWidgets":{},"title":""}},"outputs":[{"output_type":"stream","output_type":"stream","name":"stdout","text":["Out[52]: {'cod': '200',\n 'message': 0,\n 'cnt': 5,\n 'list': [{'dt': 1685394000,\n 'main': {'temp': 72.14,\n 'feels_like': 70.7,\n 'temp_min': 69.12,\n 'temp_max': 72.14,\n 'pressure': 1015,\n 'sea_level': 1015,\n 'grnd_level': 852,\n 'humidity': 35,\n 'temp_kf': 1.68},\n 'weather': [{'id': 800,\n 'main': 'Clear',\n 'description': 'clear sky',\n 'icon': '01d'}],\n 'clouds': {'all': 0},\n 'wind': {'speed': 11.14, 'deg': 217, 'gust': 14.92},\n 'visibility': 10000,\n 'pop': 0.31,\n 'sys': {'pod': 'd'},\n 'dt_txt': '2023-05-29 21:00:00'},\n {'dt': 1685404800,\n 'main': {'temp': 71.44,\n 'feels_like': 70.07,\n 'temp_min': 70.03,\n 'temp_max': 71.44,\n 'pressure': 1012,\n 'sea_level': 1012,\n 'grnd_level': 850,\n 'humidity': 38,\n 'temp_kf': 0.78},\n 'weather': [{'id': 801,\n 'main': 'Clouds',\n 'description': 'few clouds',\n 'icon': '02d'}],\n 'clouds': {'all': 18},\n 'wind': {'speed': 10.94, 'deg': 213, 'gust': 14.47},\n 'visibility': 10000,\n 'pop': 0.34,\n 'sys': {'pod': 'd'},\n 'dt_txt': '2023-05-30 00:00:00'},\n {'dt': 1685415600,\n 'main': {'temp': 62.2,\n 'feels_like': 60.89,\n 'temp_min': 57.24,\n 'temp_max': 62.2,\n 'pressure': 1012,\n 'sea_level': 1012,\n 'grnd_level': 850,\n 'humidity': 59,\n 'temp_kf': 2.76},\n 'weather': [{'id': 500,\n 'main': 'Rain',\n 'description': 'light rain',\n 'icon': '10n'}],\n 'clouds': {'all': 56},\n 'wind': {'speed': 8.81, 'deg': 209, 'gust': 17.58},\n 'visibility': 10000,\n 'pop': 0.71,\n 'rain': {'3h': 0.13},\n 'sys': {'pod': 'n'},\n 'dt_txt': '2023-05-30 03:00:00'},\n {'dt': 1685426400,\n 'main': {'temp': 50.13,\n 'feels_like': 48.79,\n 'temp_min': 50.13,\n 'temp_max': 50.13,\n 'pressure': 1012,\n 'sea_level': 1012,\n 'grnd_level': 849,\n 'humidity': 84,\n 'temp_kf': 0},\n 'weather': [{'id': 803,\n 'main': 'Clouds',\n 'description': 'broken clouds',\n 'icon': '04n'}],\n 'clouds': {'all': 68},\n 'wind': {'speed': 7.38, 'deg': 135, 'gust': 10.76},\n 'visibility': 10000,\n 'pop': 0.42,\n 'sys': {'pod': 'n'},\n 'dt_txt': '2023-05-30 06:00:00'},\n {'dt': 1685437200,\n 'main': {'temp': 48.56,\n 'feels_like': 46.87,\n 'temp_min': 48.56,\n 'temp_max': 48.56,\n 'pressure': 1012,\n 'sea_level': 1012,\n 'grnd_level': 848,\n 'humidity': 82,\n 'temp_kf': 0},\n 'weather': [{'id': 804,\n 'main': 'Clouds',\n 'description': 'overcast clouds',\n 'icon': '04n'}],\n 'clouds': {'all': 99},\n 'wind': {'speed': 4.47, 'deg': 71, 'gust': 4.76},\n 'visibility': 10000,\n 'pop': 0.05,\n 'sys': {'pod': 'n'},\n 'dt_txt': '2023-05-30 09:00:00'}],\n 'city': {'id': 5605242,\n 'name': 'Rexburg',\n 'coord': {'lat': 43.8254, 'lon': -111.7928},\n 'country': 'US',\n 'population': 25484,\n 'timezone': -21600,\n 'sunrise': 1685360996,\n 'sunset': 1685415573}}"]}],"execution_count":0},{"cell_type":"code","source":["# Google: python print json.loads object -> https://www.digitalocean.com/community/tutorials/python-pretty-print-json\njson_formatted_str = json.dumps(data, indent=3)\nprint(json_formatted_str)"],"metadata":{"application/vnd.databricks.v1+cell":{"showTitle":false,"cellMetadata":{"rowLimit":10000,"byteLimit":2048000},"nuid":"edec49b8-ef94-422b-af64-691090a95d1b","inputWidgets":{},"title":""}},"outputs":[{"output_type":"stream","output_type":"stream","name":"stdout","text":["{\n \"cod\": \"200\",\n \"message\": 0,\n \"cnt\": 5,\n \"list\": [\n {\n \"dt\": 1685394000,\n \"main\": {\n \"temp\": 72.14,\n \"feels_like\": 70.7,\n \"temp_min\": 69.12,\n \"temp_max\": 72.14,\n \"pressure\": 1015,\n \"sea_level\": 1015,\n \"grnd_level\": 852,\n \"humidity\": 35,\n \"temp_kf\": 1.68\n },\n \"weather\": [\n {\n \"id\": 800,\n \"main\": \"Clear\",\n \"description\": \"clear sky\",\n \"icon\": \"01d\"\n }\n ],\n \"clouds\": {\n \"all\": 0\n },\n \"wind\": {\n \"speed\": 11.14,\n \"deg\": 217,\n \"gust\": 14.92\n },\n \"visibility\": 10000,\n \"pop\": 0.31,\n \"sys\": {\n \"pod\": \"d\"\n },\n \"dt_txt\": \"2023-05-29 21:00:00\"\n },\n {\n \"dt\": 1685404800,\n \"main\": {\n \"temp\": 71.44,\n \"feels_like\": 70.07,\n \"temp_min\": 70.03,\n \"temp_max\": 71.44,\n \"pressure\": 1012,\n \"sea_level\": 1012,\n \"grnd_level\": 850,\n \"humidity\": 38,\n \"temp_kf\": 0.78\n },\n \"weather\": [\n {\n \"id\": 801,\n \"main\": \"Clouds\",\n \"description\": \"few clouds\",\n \"icon\": \"02d\"\n }\n ],\n \"clouds\": {\n \"all\": 18\n },\n \"wind\": {\n \"speed\": 10.94,\n \"deg\": 213,\n \"gust\": 14.47\n },\n \"visibility\": 10000,\n \"pop\": 0.34,\n \"sys\": {\n \"pod\": \"d\"\n },\n \"dt_txt\": \"2023-05-30 00:00:00\"\n },\n {\n \"dt\": 1685415600,\n \"main\": {\n \"temp\": 62.2,\n \"feels_like\": 60.89,\n \"temp_min\": 57.24,\n \"temp_max\": 62.2,\n \"pressure\": 1012,\n \"sea_level\": 1012,\n \"grnd_level\": 850,\n \"humidity\": 59,\n \"temp_kf\": 2.76\n },\n \"weather\": [\n {\n \"id\": 500,\n \"main\": \"Rain\",\n \"description\": \"light rain\",\n \"icon\": \"10n\"\n }\n ],\n \"clouds\": {\n \"all\": 56\n },\n \"wind\": {\n \"speed\": 8.81,\n \"deg\": 209,\n \"gust\": 17.58\n },\n \"visibility\": 10000,\n \"pop\": 0.71,\n \"rain\": {\n \"3h\": 0.13\n },\n \"sys\": {\n \"pod\": \"n\"\n },\n \"dt_txt\": \"2023-05-30 03:00:00\"\n },\n {\n \"dt\": 1685426400,\n \"main\": {\n \"temp\": 50.13,\n \"feels_like\": 48.79,\n \"temp_min\": 50.13,\n \"temp_max\": 50.13,\n \"pressure\": 1012,\n \"sea_level\": 1012,\n \"grnd_level\": 849,\n \"humidity\": 84,\n \"temp_kf\": 0\n },\n \"weather\": [\n {\n \"id\": 803,\n \"main\": \"Clouds\",\n \"description\": \"broken clouds\",\n \"icon\": \"04n\"\n }\n ],\n \"clouds\": {\n \"all\": 68\n },\n \"wind\": {\n \"speed\": 7.38,\n \"deg\": 135,\n \"gust\": 10.76\n },\n \"visibility\": 10000,\n \"pop\": 0.42,\n \"sys\": {\n \"pod\": \"n\"\n },\n \"dt_txt\": \"2023-05-30 06:00:00\"\n },\n {\n \"dt\": 1685437200,\n \"main\": {\n \"temp\": 48.56,\n \"feels_like\": 46.87,\n \"temp_min\": 48.56,\n \"temp_max\": 48.56,\n \"pressure\": 1012,\n \"sea_level\": 1012,\n \"grnd_level\": 848,\n \"humidity\": 82,\n \"temp_kf\": 0\n },\n \"weather\": [\n {\n \"id\": 804,\n \"main\": \"Clouds\",\n \"description\": \"overcast clouds\",\n \"icon\": \"04n\"\n }\n ],\n \"clouds\": {\n \"all\": 99\n },\n \"wind\": {\n \"speed\": 4.47,\n \"deg\": 71,\n \"gust\": 4.76\n },\n \"visibility\": 10000,\n \"pop\": 0.05,\n \"sys\": {\n \"pod\": \"n\"\n },\n \"dt_txt\": \"2023-05-30 09:00:00\"\n }\n ],\n \"city\": {\n \"id\": 5605242,\n \"name\": \"Rexburg\",\n \"coord\": {\n \"lat\": 43.8254,\n \"lon\": -111.7928\n },\n \"country\": \"US\",\n \"population\": 25484,\n \"timezone\": -21600,\n \"sunrise\": 1685360996,\n \"sunset\": 1685415573\n }\n}\n"]}],"execution_count":0},{"cell_type":"code","source":["rdd = spark.sparkContext.parallelize([response.text])\ndf = spark.read.json(rdd)\ndisplay(df)"],"metadata":{"application/vnd.databricks.v1+cell":{"showTitle":false,"cellMetadata":{"rowLimit":10000,"byteLimit":2048000},"nuid":"c5f049d1-f2c8-417c-ac22-129eec2425d8","inputWidgets":{},"title":""}},"outputs":[{"output_type":"display_data","metadata":{"application/vnd.databricks.v1+output":{"overflow":false,"datasetInfos":[],"data":[[[[43.8254,-111.7928],"US",5605242,"Rexburg",25484,1685360996,1685415573,-21600],5,"200",[[[0],1685394000,"2023-05-29 21:00:00",[70.7,852,35,1015,1015,72.14,1.68,72.14,69.12],0.31,null,["d"],10000,[["clear sky","01d",800,"Clear"]],[217,14.92,11.14]],[[18],1685404800,"2023-05-30 00:00:00",[70.07,850,38,1012,1012,71.44,0.78,71.44,70.03],0.34,null,["d"],10000,[["few clouds","02d",801,"Clouds"]],[213,14.47,10.94]],[[56],1685415600,"2023-05-30 03:00:00",[60.89,850,59,1012,1012,62.2,2.76,62.2,57.24],0.71,[0.13],["n"],10000,[["light rain","10n",500,"Rain"]],[209,17.58,8.81]],[[68],1685426400,"2023-05-30 06:00:00",[48.79,849,84,1012,1012,50.13,0.0,50.13,50.13],0.42,null,["n"],10000,[["broken clouds","04n",803,"Clouds"]],[135,10.76,7.38]],[[99],1685437200,"2023-05-30 09:00:00",[46.87,848,82,1012,1012,48.56,0.0,48.56,48.56],0.05,null,["n"],10000,[["overcast clouds","04n",804,"Clouds"]],[71,4.76,4.47]]],0]],"plotOptions":{"displayType":"table","customPlotOptions":{},"pivotColumns":null,"pivotAggregation":null,"xColumns":null,"yColumns":null},"columnCustomDisplayInfos":{},"aggType":"","isJsonSchema":true,"removedWidgets":[],"aggSchema":[],"schema":[{"name":"city","type":"{\"type\":\"struct\",\"fields\":[{\"name\":\"coord\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"lat\",\"type\":\"double\",\"nullable\":true,\"metadata\":{}},{\"name\":\"lon\",\"type\":\"double\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"country\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"id\",\"type\":\"long\",\"nullable\":true,\"metadata\":{}},{\"name\":\"name\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"population\",\"type\":\"long\",\"nullable\":true,\"metadata\":{}},{\"name\":\"sunrise\",\"type\":\"long\",\"nullable\":true,\"metadata\":{}},{\"name\":\"sunset\",\"type\":\"long\",\"nullable\":true,\"metadata\":{}},{\"name\":\"timezone\",\"type\":\"long\",\"nullable\":true,\"metadata\":{}}]}","metadata":"{}"},{"name":"cnt","type":"\"long\"","metadata":"{}"},{"name":"cod","type":"\"string\"","metadata":"{}"},{"name":"list","type":"{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"clouds\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"all\",\"type\":\"long\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"dt\",\"type\":\"long\",\"nullable\":true,\"metadata\":{}},{\"name\":\"dt_txt\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"main\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"feels_like\",\"type\":\"double\",\"nullable\":true,\"metadata\":{}},{\"name\":\"grnd_level\",\"type\":\"long\",\"nullable\":true,\"metadata\":{}},{\"name\":\"humidity\",\"type\":\"long\",\"nullable\":true,\"metadata\":{}},{\"name\":\"pressure\",\"type\":\"long\",\"nullable\":true,\"metadata\":{}},{\"name\":\"sea_level\",\"type\":\"long\",\"nullable\":true,\"metadata\":{}},{\"name\":\"temp\",\"type\":\"double\",\"nullable\":true,\"metadata\":{}},{\"name\":\"temp_kf\",\"type\":\"double\",\"nullable\":true,\"metadata\":{}},{\"name\":\"temp_max\",\"type\":\"double\",\"nullable\":true,\"metadata\":{}},{\"name\":\"temp_min\",\"type\":\"double\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"pop\",\"type\":\"double\",\"nullable\":true,\"metadata\":{}},{\"name\":\"rain\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"3h\",\"type\":\"double\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"sys\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"pod\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"visibility\",\"type\":\"long\",\"nullable\":true,\"metadata\":{}},{\"name\":\"weather\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"description\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"icon\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"id\",\"type\":\"long\",\"nullable\":true,\"metadata\":{}},{\"name\":\"main\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"wind\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"deg\",\"type\":\"long\",\"nullable\":true,\"metadata\":{}},{\"name\":\"gust\",\"type\":\"double\",\"nullable\":true,\"metadata\":{}},{\"name\":\"speed\",\"type\":\"double\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true}","metadata":"{}"},{"name":"message","type":"\"long\"","metadata":"{}"}],"aggError":"","aggData":[],"addedWidgets":{},"metadata":{},"dbfsResultPath":null,"type":"table","aggOverflow":false,"aggSeriesLimitReached":false,"arguments":{}}},"output_type":"display_data","data":{"text/html":["<style scoped>\n"," .table-result-container {\n"," max-height: 300px;\n"," overflow: auto;\n"," }\n"," table, th, td {\n"," border: 1px solid black;\n"," border-collapse: collapse;\n"," }\n"," th, td {\n"," padding: 5px;\n"," }\n"," th {\n"," text-align: left;\n"," }\n","</style><div class='table-result-container'><table class='table-result'><thead style='background-color: white'><tr><th>city</th><th>cnt</th><th>cod</th><th>list</th><th>message</th></tr></thead><tbody><tr><td>List(List(43.8254, -111.7928), US, 5605242, Rexburg, 25484, 1685360996, 1685415573, -21600)</td><td>5</td><td>200</td><td>List(List(List(0), 1685394000, 2023-05-29 21:00:00, List(70.7, 852, 35, 1015, 1015, 72.14, 1.68, 72.14, 69.12), 0.31, null, List(d), 10000, List(List(clear sky, 01d, 800, Clear)), List(217, 14.92, 11.14)), List(List(18), 1685404800, 2023-05-30 00:00:00, List(70.07, 850, 38, 1012, 1012, 71.44, 0.78, 71.44, 70.03), 0.34, null, List(d), 10000, List(List(few clouds, 02d, 801, Clouds)), List(213, 14.47, 10.94)), List(List(56), 1685415600, 2023-05-30 03:00:00, List(60.89, 850, 59, 1012, 1012, 62.2, 2.76, 62.2, 57.24), 0.71, List(0.13), List(n), 10000, List(List(light rain, 10n, 500, Rain)), List(209, 17.58, 8.81)), List(List(68), 1685426400, 2023-05-30 06:00:00, List(48.79, 849, 84, 1012, 1012, 50.13, 0.0, 50.13, 50.13), 0.42, null, List(n), 10000, List(List(broken clouds, 04n, 803, Clouds)), List(135, 10.76, 7.38)), List(List(99), 1685437200, 2023-05-30 09:00:00, List(46.87, 848, 82, 1012, 1012, 48.56, 0.0, 48.56, 48.56), 0.05, null, List(n), 10000, List(List(overcast clouds, 04n, 804, Clouds)), List(71, 4.76, 4.47)))</td><td>0</td></tr></tbody></table></div>"]}}],"execution_count":0},{"cell_type":"code","source":["df.printSchema()"],"metadata":{"application/vnd.databricks.v1+cell":{"showTitle":false,"cellMetadata":{"rowLimit":10000,"byteLimit":2048000},"nuid":"de7ca8e6-1e75-4461-bd45-310cabbc3bfb","inputWidgets":{},"title":""}},"outputs":[{"output_type":"stream","output_type":"stream","name":"stdout","text":["root\n |-- city: struct (nullable = true)\n | |-- coord: struct (nullable = true)\n | | |-- lat: double (nullable = true)\n | | |-- lon: double (nullable = true)\n | |-- country: string (nullable = true)\n | |-- id: long (nullable = true)\n | |-- name: string (nullable = true)\n | |-- population: long (nullable = true)\n | |-- sunrise: long (nullable = true)\n | |-- sunset: long (nullable = true)\n | |-- timezone: long (nullable = true)\n |-- cnt: long (nullable = true)\n |-- cod: string (nullable = true)\n |-- list: array (nullable = true)\n | |-- element: struct (containsNull = true)\n | | |-- clouds: struct (nullable = true)\n | | | |-- all: long (nullable = true)\n | | |-- dt: long (nullable = true)\n | | |-- dt_txt: string (nullable = true)\n | | |-- main: struct (nullable = true)\n | | | |-- feels_like: double (nullable = true)\n | | | |-- grnd_level: long (nullable = true)\n | | | |-- humidity: long (nullable = true)\n | | | |-- pressure: long (nullable = true)\n | | | |-- sea_level: long (nullable = true)\n | | | |-- temp: double (nullable = true)\n | | | |-- temp_kf: double (nullable = true)\n | | | |-- temp_max: double (nullable = true)\n | | | |-- temp_min: double (nullable = true)\n | | |-- pop: double (nullable = true)\n | | |-- rain: struct (nullable = true)\n | | | |-- 3h: double (nullable = true)\n | | |-- sys: struct (nullable = true)\n | | | |-- pod: string (nullable = true)\n | | |-- visibility: long (nullable = true)\n | | |-- weather: array (nullable = true)\n | | | |-- element: struct (containsNull = true)\n | | | | |-- description: string (nullable = true)\n | | | | |-- icon: string (nullable = true)\n | | | | |-- id: long (nullable = true)\n | | | | |-- main: string (nullable = true)\n | | |-- wind: struct (nullable = true)\n | | | |-- deg: long (nullable = true)\n | | | |-- gust: double (nullable = true)\n | | | |-- speed: double (nullable = true)\n |-- message: long (nullable = true)\n\n"]}],"execution_count":0}],"metadata":{"application/vnd.databricks.v1+notebook":{"notebookName":"example_api","dashboards":[],"notebookMetadata":{"pythonIndentUnit":2},"language":"python","widgets":{}}},"nbformat":4,"nbformat_minor":0}
|
APIs/graphql_noauth.py
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# %%
|
2 |
+
# https://towardsdatascience.com/connecting-to-a-graphql-api-using-python-246dda927840
|
3 |
+
import requests
|
4 |
+
import json
|
5 |
+
import pandas as pd
|
6 |
+
# %%
|
7 |
+
query = """
|
8 |
+
query {
|
9 |
+
characters {
|
10 |
+
results {
|
11 |
+
name
|
12 |
+
status
|
13 |
+
species
|
14 |
+
type
|
15 |
+
gender
|
16 |
+
}
|
17 |
+
}
|
18 |
+
}
|
19 |
+
"""
|
20 |
+
# %%
|
21 |
+
url = 'https://rickandmortyapi.com/graphql/'
|
22 |
+
r = requests.post(url, json={'query': query})
|
23 |
+
print(r.status_code)
|
24 |
+
print(r.text)
|
25 |
+
# %%
|
26 |
+
json_data = json.loads(r.text)
|
27 |
+
# %%
|
28 |
+
df_data = json_data['data']['characters']['results']
|
29 |
+
df = pd.DataFrame(df_data)
|
30 |
+
# %%
|
APIs/graphql_noauth_activity.py
ADDED
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# %%
|
2 |
+
# https://towardsdatascience.com/connecting-to-a-graphql-api-using-python-246dda927840
|
3 |
+
import requests
|
4 |
+
import json
|
5 |
+
import jsonlines
|
6 |
+
import pandas as pd
|
7 |
+
import pyarrow as pa
|
8 |
+
import pyarrow.json as pj
|
9 |
+
# %%
|
10 |
+
query = """
|
11 |
+
query {
|
12 |
+
characters {
|
13 |
+
results {
|
14 |
+
name
|
15 |
+
status
|
16 |
+
species
|
17 |
+
type
|
18 |
+
gender
|
19 |
+
}
|
20 |
+
}
|
21 |
+
}
|
22 |
+
"""
|
23 |
+
|
24 |
+
query = """
|
25 |
+
query {
|
26 |
+
characters {
|
27 |
+
results {
|
28 |
+
name
|
29 |
+
status
|
30 |
+
species
|
31 |
+
type
|
32 |
+
gender
|
33 |
+
image
|
34 |
+
episode{
|
35 |
+
episode
|
36 |
+
name
|
37 |
+
air_date
|
38 |
+
}
|
39 |
+
}
|
40 |
+
}
|
41 |
+
}
|
42 |
+
"""
|
43 |
+
# %%
|
44 |
+
url = 'https://rickandmortyapi.com/graphql/'
|
45 |
+
r = requests.post(url, json={'query': query})
|
46 |
+
print(r.status_code)
|
47 |
+
print(r.text)
|
48 |
+
# %%
|
49 |
+
json_data = json.loads(r.text)
|
50 |
+
# %%
|
51 |
+
df_data = json_data['data']['characters']['results']
|
52 |
+
df = pd.json_normalize(df_data)
|
53 |
+
|
54 |
+
|
55 |
+
# %%
|
56 |
+
# https://blog.softhints.com/python-convert-json-to-json-lines/
|
57 |
+
with jsonlines.open('output.jsonl', mode='w') as writer:
|
58 |
+
writer.write_all(df_data)
|
59 |
+
|
60 |
+
# %%
|
61 |
+
datjl = pd.read_json("output.jsonl", lines=True)
|
62 |
+
|
63 |
+
# %%
|
64 |
+
pj.read_json("output.jsonl", parse_options = pj.ParseOptions(newlines_in_values=True))
|
65 |
+
|
66 |
+
# %%
|
67 |
+
# if we want to convert the format to JSON arrays.
|
68 |
+
|
69 |
+
# df_data = json_data['data']['characters']['results']
|
70 |
+
# for i, value in enumerate(df_data):
|
71 |
+
# print(i)
|
72 |
+
# idat = df_data[i].copy()
|
73 |
+
# number = list()
|
74 |
+
# name = list()
|
75 |
+
# airdate = list()
|
76 |
+
# for j, jvalue in enumerate(idat['episode']):
|
77 |
+
# iseason = idat['episode'][j]
|
78 |
+
# number.append(iseason['episode'])
|
79 |
+
# name.append(iseason['name'])
|
80 |
+
# airdate.append(iseason['air_date'])
|
81 |
+
# df_data[i]['episode'] = {
|
82 |
+
# 'number':number,
|
83 |
+
# "name":name,
|
84 |
+
# "air_date":airdate}
|
APIs/openweather.md
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# [OpenWeather.org](https://openweathermap.org/)
|
2 |
+
|
3 |
+
As academics we get free access to the [OpenWeather API](https://openweathermap.org/our-initiatives/student-initiative).
|
4 |
+
|
5 |
+
We get the benefits of the _Developer Plan_ for [current weather and forecasts](https://openweathermap.org/price#current) and the _Medium Plan_ for the [historical weather collection](https://openweathermap.org/price#history).
|
6 |
+
|
7 |
+
You have to fill out the request form to get access to the “Developer Plan” for Open Weather API for free as a student. If you don’t sign up, you get this error when trying to access it in your code:
|
8 |
+
|
9 |
+
```
|
10 |
+
{'cod': 401, 'message': 'Invalid API key. Please see https://openweathermap.org/faq#error401 for more info.'}
|
11 |
+
```
|
12 |
+
|
13 |
+
So just fill out the form at this site to get access: https://openweathermap.org/our-initiatives/student-initiative.
|
14 |
+
Then you should see the Developer plan for Weather and Medium plan for History on the Services tab: https://home.openweathermap.org/myservices.
|
pages/__pycache__/compare_locations.cpython-311.pyc
ADDED
Binary file (5.08 kB). View file
|
|
pages/__pycache__/compare_locations.cpython-39.pyc
ADDED
Binary file (2.58 kB). View file
|
|
pages/__pycache__/single_location.cpython-311.pyc
ADDED
Binary file (2.12 kB). View file
|
|
pages/__pycache__/single_location.cpython-39.pyc
ADDED
Binary file (1.06 kB). View file
|
|
pages/__pycache__/summary_metrics.cpython-311.pyc
ADDED
Binary file (6.14 kB). View file
|
|
pages/__pycache__/summary_metrics.cpython-39.pyc
ADDED
Binary file (2.93 kB). View file
|
|
pages/__pycache__/variable_selection.cpython-311.pyc
ADDED
Binary file (6.66 kB). View file
|
|
pages/__pycache__/variable_selection.cpython-39.pyc
ADDED
Binary file (3.22 kB). View file
|
|
pages/compare_locations.py
ADDED
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import polars as pl
|
3 |
+
from utils.weather_api import fetch_weather
|
4 |
+
from datetime import datetime, timedelta
|
5 |
+
import matplotlib.pyplot as plt
|
6 |
+
|
7 |
+
def fetch_multiple_cities(start_date, end_date):
|
8 |
+
locations = {
|
9 |
+
"BYU Idaho": {"lat": 43.8145, "lon": -111.7833},
|
10 |
+
"BYU Hawaii": {"lat": 21.6419, "lon": -157.9267},
|
11 |
+
"BYU Provo": {"lat": 40.25, "lon": -111.65}
|
12 |
+
}
|
13 |
+
city_data = {}
|
14 |
+
for city, coords in locations.items():
|
15 |
+
try:
|
16 |
+
df = fetch_weather(coords, start_date, end_date)
|
17 |
+
city_data[city] = df.rename({"temperature_2m": city})
|
18 |
+
except Exception as e:
|
19 |
+
st.error(f"Failed to fetch data for {city}: {e}")
|
20 |
+
|
21 |
+
combined_df = city_data[list(city_data.keys())[0]].select("datetime")
|
22 |
+
for city, data in city_data.items():
|
23 |
+
combined_df = pl.concat([combined_df, data.select(city)], how="horizontal")
|
24 |
+
return combined_df
|
25 |
+
|
26 |
+
def display_visualizations(df):
|
27 |
+
st.subheader("Daily High Temperatures Across Cities")
|
28 |
+
melted_df = df.melt(id_vars=["datetime"], variable_name="City", value_name="Temperature")
|
29 |
+
|
30 |
+
plt.figure(figsize=(10, 6))
|
31 |
+
for city in df.columns[1:]:
|
32 |
+
plt.plot(df["datetime"], df[city], label=city)
|
33 |
+
plt.xlabel("Datetime")
|
34 |
+
plt.ylabel("Temperature (°F)")
|
35 |
+
plt.title("Temperature Comparison")
|
36 |
+
plt.legend()
|
37 |
+
st.pyplot(plt)
|
38 |
+
|
39 |
+
def calculate_kpis(df):
|
40 |
+
kpis = {}
|
41 |
+
for city in df.columns[1:]:
|
42 |
+
highest = df[city].max()
|
43 |
+
lowest = df[city].min()
|
44 |
+
kpis[city] = (highest, lowest)
|
45 |
+
return kpis
|
46 |
+
|
47 |
+
def main():
|
48 |
+
st.title("Compare Weather Data for All BYU Locations 🌍")
|
49 |
+
st.sidebar.header("User Input")
|
50 |
+
start_date = st.sidebar.date_input("Start Date", datetime.now() - timedelta(days=15))
|
51 |
+
end_date = st.sidebar.date_input("End Date", datetime.now())
|
52 |
+
|
53 |
+
if start_date and end_date:
|
54 |
+
st.write("Fetching weather data for all BYU locations...")
|
55 |
+
try:
|
56 |
+
combined_data = fetch_multiple_cities(start_date, end_date)
|
57 |
+
st.dataframe(combined_data)
|
58 |
+
|
59 |
+
st.subheader("Key Performance Indicators (KPIs)")
|
60 |
+
kpis = calculate_kpis(combined_data)
|
61 |
+
for city, (highest, lowest) in kpis.items():
|
62 |
+
st.write(f"**{city}:** Highest Temp: {highest:.2f} °F | Lowest Temp: {lowest:.2f} °F")
|
63 |
+
|
64 |
+
display_visualizations(combined_data)
|
65 |
+
except Exception as e:
|
66 |
+
st.error(f"Error: {e}")
|
pages/single_location.py
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
from utils.weather_api import fetch_weather
|
3 |
+
from datetime import datetime, timedelta
|
4 |
+
|
5 |
+
def main():
|
6 |
+
st.title("Weather Dashboard for Single BYU Location 🌤️")
|
7 |
+
|
8 |
+
locations = {
|
9 |
+
"BYU Idaho": {"lat": 43.8145, "lon": -111.7833},
|
10 |
+
"BYU Hawaii": {"lat": 21.6419, "lon": -157.9267},
|
11 |
+
"BYU Provo": {"lat": 40.25, "lon": -111.65}
|
12 |
+
}
|
13 |
+
|
14 |
+
st.sidebar.header("User Input")
|
15 |
+
selected_city = st.sidebar.selectbox("Select a BYU Location", list(locations.keys()))
|
16 |
+
start_date = st.sidebar.date_input("Start Date", datetime.now() - timedelta(days=15))
|
17 |
+
end_date = st.sidebar.date_input("End Date", datetime.now())
|
18 |
+
|
19 |
+
if start_date and end_date:
|
20 |
+
try:
|
21 |
+
weather_data = fetch_weather(locations[selected_city], start_date, end_date)
|
22 |
+
st.write(f"Weather Data for **{selected_city}**")
|
23 |
+
st.dataframe(weather_data)
|
24 |
+
except Exception as e:
|
25 |
+
st.error(f"Error fetching data: {e}")
|
pages/summary_metrics.py
ADDED
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import polars as pl
|
3 |
+
from datetime import datetime, timedelta
|
4 |
+
from utils.weather_api import fetch_weather
|
5 |
+
|
6 |
+
locations = {
|
7 |
+
"BYU Idaho": {"lat": 43.8145, "lon": -111.7833},
|
8 |
+
"BYU Hawaii": {"lat": 21.6419, "lon": -157.9267},
|
9 |
+
"BYU Provo": {"lat": 40.25, "lon": -111.65}
|
10 |
+
}
|
11 |
+
|
12 |
+
weather_variables = {
|
13 |
+
"Temperature (°F)": "temperature_2m",
|
14 |
+
"Wind Speed (mph)": "windspeed_10m",
|
15 |
+
"Relative Humidity (%)": "relative_humidity_2m"
|
16 |
+
}
|
17 |
+
|
18 |
+
def fetch_multiple_cities(start_date, end_date, variable, timezone_selection):
|
19 |
+
city_data = {}
|
20 |
+
for city, coords in locations.items():
|
21 |
+
df = fetch_weather(coords, start_date, end_date, variable, timezone_selection)
|
22 |
+
city_data[city] = df.rename({variable: city})
|
23 |
+
combined_df = city_data[list(city_data.keys())[0]].select("datetime")
|
24 |
+
for city, data in city_data.items():
|
25 |
+
combined_df = pl.concat([combined_df, data.select(city)], how="horizontal")
|
26 |
+
return combined_df
|
27 |
+
|
28 |
+
def calculate_summary_metrics(df):
|
29 |
+
avg_values = df.select([pl.mean(city).alias(city) for city in df.columns if city != "datetime"])
|
30 |
+
max_values = df.select([pl.max(city).alias(city) for city in df.columns if city != "datetime"])
|
31 |
+
min_values = df.select([pl.min(city).alias(city) for city in df.columns if city != "datetime"])
|
32 |
+
|
33 |
+
avg_summary = avg_values.row(0)
|
34 |
+
max_summary = max_values.row(0)
|
35 |
+
min_summary = min_values.row(0)
|
36 |
+
return avg_summary, max_summary, min_summary
|
37 |
+
|
38 |
+
def main():
|
39 |
+
st.title("Compare Weather Metrics Across BYU Locations 📊")
|
40 |
+
|
41 |
+
st.sidebar.header("User Input")
|
42 |
+
start_date = st.sidebar.date_input("Start Date", datetime.now() - timedelta(days=15))
|
43 |
+
end_date = st.sidebar.date_input("End Date", datetime.now())
|
44 |
+
selected_variable_label = st.sidebar.selectbox("Select Weather Variable", weather_variables.keys())
|
45 |
+
timezone_selection = st.sidebar.radio("Select Time Zone", ["America/Denver", "Pacific/Honolulu"])
|
46 |
+
selected_variable = weather_variables[selected_variable_label]
|
47 |
+
|
48 |
+
if start_date and end_date:
|
49 |
+
with st.spinner("Fetching weather data across all BYU locations..."):
|
50 |
+
combined_data = fetch_multiple_cities(start_date, end_date, selected_variable, timezone_selection)
|
51 |
+
|
52 |
+
st.write(f"### {selected_variable_label} Data Across Locations")
|
53 |
+
st.dataframe(combined_data)
|
54 |
+
|
55 |
+
avg_summary, max_summary, min_summary = calculate_summary_metrics(combined_data)
|
56 |
+
|
57 |
+
st.subheader("Key Performance Indicators (KPIs)")
|
58 |
+
kpi_cols = st.columns(len(locations))
|
59 |
+
for idx, city in enumerate(locations):
|
60 |
+
with kpi_cols[idx]:
|
61 |
+
st.metric(f"{city} - Avg", f"{avg_summary[idx]:.2f}")
|
62 |
+
st.metric(f"{city} - Max", f"{max_summary[idx]:.2f}")
|
63 |
+
st.metric(f"{city} - Min", f"{min_summary[idx]:.2f}")
|
64 |
+
|
65 |
+
if __name__ == "__main__":
|
66 |
+
main()
|
pages/variable_selection.py
ADDED
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import polars as pl
|
3 |
+
from datetime import datetime, timedelta
|
4 |
+
from utils.weather_api import fetch_weather
|
5 |
+
import matplotlib.pyplot as plt
|
6 |
+
|
7 |
+
def main():
|
8 |
+
st.title("Compare Multiple Weather Variables Across BYU Locations 🌤️")
|
9 |
+
|
10 |
+
locations = {
|
11 |
+
"BYU Idaho": {"lat": 43.8145, "lon": -111.7833},
|
12 |
+
"BYU Hawaii": {"lat": 21.6419, "lon": -157.9267},
|
13 |
+
"BYU Provo": {"lat": 40.25, "lon": -111.65}
|
14 |
+
}
|
15 |
+
|
16 |
+
weather_variables = {
|
17 |
+
"Temperature (°F)": "temperature_2m",
|
18 |
+
"Wind Speed (mph)": "windspeed_10m",
|
19 |
+
"Relative Humidity (%)": "relative_humidity_2m"
|
20 |
+
}
|
21 |
+
|
22 |
+
def fetch_all_variables(location, start_date, end_date, timezone_selection):
|
23 |
+
dataframes = {}
|
24 |
+
for label, variable in weather_variables.items():
|
25 |
+
try:
|
26 |
+
df = fetch_weather(location, start_date, end_date, variable, timezone_selection)
|
27 |
+
df = df.rename({variable: label})
|
28 |
+
dataframes[label] = df
|
29 |
+
except Exception as e:
|
30 |
+
st.error(f"Error fetching {label} data: {e}")
|
31 |
+
return dataframes
|
32 |
+
|
33 |
+
st.sidebar.header("User Input")
|
34 |
+
start_date = st.sidebar.date_input("Start Date", datetime.now() - timedelta(days=15))
|
35 |
+
end_date = st.sidebar.date_input("End Date", datetime.now())
|
36 |
+
timezone_selection = st.sidebar.radio("Select Time Zone", ["America/Denver", "Pacific/Honolulu"])
|
37 |
+
|
38 |
+
if start_date and end_date:
|
39 |
+
st.write("Fetching all weather variables for each BYU location...")
|
40 |
+
all_data = {}
|
41 |
+
for city, coords in locations.items():
|
42 |
+
st.write(f"Fetching data for **{city}**...")
|
43 |
+
all_data[city] = fetch_all_variables(coords, start_date, end_date, timezone_selection)
|
44 |
+
|
45 |
+
st.subheader("Combined Weather Data Across Cities and Variables")
|
46 |
+
for city, dataframes in all_data.items():
|
47 |
+
st.write(f"**{city}**")
|
48 |
+
|
49 |
+
datetime_df = dataframes[list(dataframes.keys())[0]].select("datetime")
|
50 |
+
data_without_datetime = [df.drop("datetime") for df in dataframes.values()]
|
51 |
+
combined_df = pl.concat([datetime_df] + data_without_datetime, how="horizontal")
|
52 |
+
|
53 |
+
st.dataframe(combined_df)
|
54 |
+
|
55 |
+
st.subheader(f"Visualizing Weather Variables for {city}")
|
56 |
+
plt.figure(figsize=(10, 6))
|
57 |
+
for label in dataframes:
|
58 |
+
plt.plot(combined_df["datetime"], combined_df[label], label=label)
|
59 |
+
plt.xlabel("Datetime")
|
60 |
+
plt.ylabel("Values")
|
61 |
+
plt.title(f"Weather Variables for {city}")
|
62 |
+
plt.legend()
|
63 |
+
st.pyplot(plt)
|
64 |
+
|
65 |
+
st.subheader("Boxplot of Weather Variables Across All Cities")
|
66 |
+
plt.figure(figsize=(12, 8))
|
67 |
+
|
68 |
+
for i, city in enumerate(all_data.keys(), start=1):
|
69 |
+
city_data = all_data[city]
|
70 |
+
|
71 |
+
data_without_datetime = [df.drop("datetime") for df in city_data.values()]
|
72 |
+
combined_city_df = pl.concat(data_without_datetime, how="horizontal").to_pandas()
|
73 |
+
|
74 |
+
plt.boxplot(combined_city_df.values,
|
75 |
+
labels=combined_city_df.columns,
|
76 |
+
positions=[i + idx * 0.3 for idx in range(len(weather_variables))],
|
77 |
+
widths=0.25)
|
78 |
+
|
79 |
+
plt.title("Weather Variable Comparison")
|
80 |
+
plt.ylabel("Values")
|
81 |
+
plt.xticks(range(1, len(all_data.keys()) + 1), all_data.keys())
|
82 |
+
plt.grid(True)
|
83 |
+
st.pyplot(plt)
|
84 |
+
|
85 |
+
if __name__ == "__main__":
|
86 |
+
main()
|
utils/__pycache__/weather_api.cpython-311.pyc
ADDED
Binary file (1.62 kB). View file
|
|
utils/__pycache__/weather_api.cpython-39.pyc
ADDED
Binary file (990 Bytes). View file
|
|
utils/weather_api.py
ADDED
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import requests
|
2 |
+
import polars as pl
|
3 |
+
|
4 |
+
def fetch_weather(location, start_date, end_date, variable="temperature_2m", timezone="America/Denver"):
|
5 |
+
base_url = "https://api.open-meteo.com/v1/forecast"
|
6 |
+
params = {
|
7 |
+
"latitude": location['lat'],
|
8 |
+
"longitude": location['lon'],
|
9 |
+
"start_date": start_date,
|
10 |
+
"end_date": end_date,
|
11 |
+
"hourly": variable,
|
12 |
+
"timezone": timezone
|
13 |
+
}
|
14 |
+
|
15 |
+
response = requests.get(base_url, params=params)
|
16 |
+
if response.status_code == 200:
|
17 |
+
data = response.json()
|
18 |
+
hourly_data = data['hourly']
|
19 |
+
|
20 |
+
if variable == "temperature_2m":
|
21 |
+
df = pl.DataFrame({
|
22 |
+
"datetime": hourly_data['time'],
|
23 |
+
variable: [temp * 9 / 5 + 32 for temp in hourly_data[variable]]
|
24 |
+
})
|
25 |
+
else:
|
26 |
+
df = pl.DataFrame({
|
27 |
+
"datetime": hourly_data['time'],
|
28 |
+
variable: hourly_data[variable]
|
29 |
+
})
|
30 |
+
return df
|
31 |
+
else:
|
32 |
+
raise Exception(f"API request failed: {response.status_code}")
|