Dreipfelt commited on
Commit
fd69b5a
·
verified ·
1 Parent(s): ede6e69

feat/exploratory-data-analysis (#2)

Browse files

- ✨ Add the prices historical graphs (b77833c647678df675d9f6d1c71729160ca68153)

.streamlit/config.toml ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ [server]
2
+
3
+ maxMessageSize = 300
Dockerfile CHANGED
@@ -18,10 +18,11 @@ COPY --chown=user requirements.txt ./
18
  RUN pip3 install -r requirements.txt
19
 
20
  COPY --chown=user README.md ./
 
21
  COPY --chown=user src/ ./src/
22
 
23
  EXPOSE 8501
24
 
25
  HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
26
 
27
- ENTRYPOINT ["streamlit", "run", "src/app.py", "--server.port=8501", "--server.address=0.0.0.0"]
 
18
  RUN pip3 install -r requirements.txt
19
 
20
  COPY --chown=user README.md ./
21
+ COPY --chown=user .streamlit/ ./.streamlit/
22
  COPY --chown=user src/ ./src/
23
 
24
  EXPOSE 8501
25
 
26
  HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
27
 
28
+ ENTRYPOINT ["streamlit", "run", "src/Home.py", "--server.port=8501", "--server.address=0.0.0.0"]
requirements.txt CHANGED
@@ -1,3 +1,5 @@
1
  altair
2
  pandas
3
- streamlit
 
 
 
1
  altair
2
  pandas
3
+ streamlit
4
+ boto3
5
+ plotly
src/Home.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Home.py
2
+ import streamlit as st
3
+
4
+ st.set_page_config(page_title="Multi-Page App Home", page_icon="🏠", layout="centered")
5
+
6
+ # This project aims to predict real estate prices, primarily focusing on the impact of **climatic events**. Our goal is to identify **safe and profitable locations** by analyzing how various weather and climate patterns influence property values. As the project evolves, we plan to incorporate other significant events that might affect real estate prices.
7
+
8
+ st.title("Welcome to Oasis! 🏠")
9
+ st.write(
10
+ """
11
+ Oasis is a project designed to predict real estate prices, focusing on the impact of climatic events. Our goal is to identify safe and profitable locations by analyzing how various weather and climate patterns influence property values.
12
+
13
+ **How this works:**
14
+ 1. **Data Collection:** We gather data on real estate prices and climatic events.
15
+ 2. **Data Analysis:** We analyze the data to understand how different climatic factors affect property values.
16
+ 3. **Model Training:** We train machine learning models to predict real estate prices based on climatic conditions and climatic conditions.
17
+ 4. **Location Assessment:** We assess locations for safety and profitability based on our predictions.
18
+ """
19
+ )
20
+
21
+ st.info("👈 Select a page from the sidebar to get started!")
src/app.py DELETED
@@ -1,40 +0,0 @@
1
- import altair as alt
2
- import numpy as np
3
- import pandas as pd
4
- import streamlit as st
5
-
6
- """
7
- # Welcome to Streamlit!
8
-
9
- Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
10
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
11
- forums](https://discuss.streamlit.io).
12
-
13
- In the meantime, below is an example of what you can do with just a few lines of code:
14
- """
15
-
16
- num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
17
- num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
18
-
19
- indices = np.linspace(0, 1, num_points)
20
- theta = 2 * np.pi * num_turns * indices
21
- radius = indices
22
-
23
- x = radius * np.cos(theta)
24
- y = radius * np.sin(theta)
25
-
26
- df = pd.DataFrame({
27
- "x": x,
28
- "y": y,
29
- "idx": indices,
30
- "rand": np.random.randn(num_points),
31
- })
32
-
33
- st.altair_chart(alt.Chart(df, height=700, width=700)
34
- .mark_point(filled=True)
35
- .encode(
36
- x=alt.X("x", axis=None),
37
- y=alt.Y("y", axis=None),
38
- color=alt.Color("idx", legend=None, scale=alt.Scale()),
39
- size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
40
- ))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/pages/1_Historical_Price_-_France.py ADDED
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import streamlit as st
3
+ import os
4
+ import boto3
5
+ import json
6
+ import urllib.request
7
+ import io
8
+ import plotly.colors as pcolors
9
+ import plotly.express as px
10
+
11
+ AWS_S3_BUCKET = os.getenv("AWS_S3_BUCKET", "oasis-prd-001")
12
+ AWS_ACCESS_KEY_ID = os.getenv("AWS_ACCESS_KEY_ID")
13
+ AWS_SECRET_ACCESS_KEY = os.getenv("AWS_SECRET_ACCESS_KEY")
14
+
15
+ st.set_page_config(page_title="Oasis", page_icon=":house:", layout="wide")
16
+
17
+ st.header("Historical Price - France")
18
+ st.subheader("An overview of real estate prices in France from 2015 to 2024")
19
+
20
+ st.write(
21
+ "This map shows the average price per square meter in French departments over the years, with a focus on climatic events."
22
+ )
23
+
24
+
25
+ def load_file_s3(object_key: str) -> pd.DataFrame:
26
+ """Load a file from S3 and return its contents as a pandas DataFrame."""
27
+ if not AWS_S3_BUCKET or not AWS_ACCESS_KEY_ID or not AWS_SECRET_ACCESS_KEY:
28
+ raise ValueError(
29
+ "AWS credentials or bucket name not set in environment variables."
30
+ )
31
+
32
+ s3_client = boto3.client(
33
+ "s3",
34
+ aws_access_key_id=AWS_ACCESS_KEY_ID,
35
+ aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
36
+ )
37
+
38
+ response = s3_client.get_object(Bucket=AWS_S3_BUCKET, Key=object_key)
39
+ status = response.get("ResponseMetadata", {}).get("HTTPStatusCode")
40
+
41
+ if status == 200:
42
+ return pd.read_csv(io.StringIO(response["Body"].read().decode("utf-8")))
43
+ raise ValueError(f"Unsuccessful S3 get_object response. Status - {status}")
44
+
45
+
46
+ @st.cache_data
47
+ def load_geojson():
48
+ geojson_url = "https://france-geojson.gregoiredavid.fr/repo/departements.geojson"
49
+ with urllib.request.urlopen(geojson_url) as response:
50
+ departements_geojson = json.load(response)
51
+ return departements_geojson
52
+
53
+
54
+ @st.cache_data
55
+ def load_dataset_housing_prices():
56
+ df = load_file_s3("processed/housing/dataset_housing_prices.csv")
57
+ return df
58
+
59
+
60
+ @st.cache_data
61
+ def load_dataset_housing_departement_prices_full():
62
+ df = load_file_s3("processed/housing/dataset_housing_departement_prices_full.csv")
63
+ return df
64
+
65
+
66
+ #####################################################################
67
+ # Data loading
68
+ #####################################################################
69
+
70
+ dataset_housing_prices = load_dataset_housing_prices()
71
+ dataset_housing_departement_prices_full = load_dataset_housing_departement_prices_full()
72
+ departements_geojson = load_geojson()
73
+
74
+ #####################################################################
75
+ # Data processing
76
+ #####################################################################
77
+
78
+ MISSING_VALUE_PLACEHOLDER = -1
79
+ dataset_departements_housing_prices = (
80
+ dataset_housing_prices.groupby(["code_departement", "annee"])["prixm2moyen"]
81
+ .mean()
82
+ .reset_index()
83
+ )
84
+ min_actual_departement_prixm2moyen = dataset_departements_housing_prices[
85
+ "prixm2moyen"
86
+ ].min()
87
+ max_actual_departement_prixm2moyen = dataset_departements_housing_prices[
88
+ "prixm2moyen"
89
+ ].max()
90
+
91
+ missing_rows = dataset_housing_departement_prices_full[
92
+ ~dataset_housing_departement_prices_full.set_index(
93
+ ["code_departement", "annee"]
94
+ ).index.isin(
95
+ dataset_departements_housing_prices.set_index(
96
+ ["code_departement", "annee"]
97
+ ).index
98
+ )
99
+ ]
100
+
101
+ missing_rows = missing_rows[["code_departement", "annee"]]
102
+ missing_rows["prixm2moyen"] = (
103
+ MISSING_VALUE_PLACEHOLDER # Set a default value for prixm2moyen
104
+ )
105
+
106
+ dataset_departements_housing_prices = pd.concat(
107
+ [dataset_departements_housing_prices, missing_rows], ignore_index=True
108
+ )
109
+
110
+ #####################################################################
111
+ # Graphical representation of the data
112
+ #####################################################################
113
+
114
+ color_range_min = MISSING_VALUE_PLACEHOLDER
115
+ color_range_max = max_actual_departement_prixm2moyen
116
+
117
+ normalized_min_actual = (min_actual_departement_prixm2moyen - color_range_min) / (
118
+ color_range_max - color_range_min
119
+ )
120
+ normalized_max_actual = (max_actual_departement_prixm2moyen - color_range_min) / (
121
+ color_range_max - color_range_min
122
+ )
123
+
124
+ custom_colorscale = []
125
+ # Add the color for missing values
126
+ custom_colorscale.append([0.0, "lightgrey"])
127
+ reversed_rdylgn_colors = pcolors.diverging.RdYlGn[::-1] # <--- Correct way to reverse
128
+ # Add the reversed RdYlGn colors for the actual data range
129
+ num_steps = len(reversed_rdylgn_colors)
130
+ for i, color in enumerate(reversed_rdylgn_colors):
131
+ normalized_point = normalized_min_actual + (
132
+ normalized_max_actual - normalized_min_actual
133
+ ) * (i / (num_steps - 1))
134
+ if normalized_point > 0.0: # Ensure we don't overwrite the grey for missing
135
+ custom_colorscale.append([normalized_point, color])
136
+
137
+ # Sort the custom_colorscale by the normalized value to ensure correct order
138
+ custom_colorscale = sorted(custom_colorscale, key=lambda x: x[0])
139
+
140
+ fig = px.choropleth_map(
141
+ dataset_departements_housing_prices,
142
+ geojson=departements_geojson,
143
+ locations="code_departement",
144
+ featureidkey="properties.code",
145
+ color="prixm2moyen",
146
+ range_color=[
147
+ min_actual_departement_prixm2moyen,
148
+ max_actual_departement_prixm2moyen,
149
+ ],
150
+ color_continuous_scale=custom_colorscale,
151
+ center={"lat": 46.6, "lon": 2.6},
152
+ zoom=5,
153
+ opacity=0.75,
154
+ hover_name="code_departement",
155
+ hover_data={
156
+ "prixm2moyen": ":.0f",
157
+ "annee": True, # Include year in hover data
158
+ },
159
+ title="Average Price per Square Meter in French Departments (2015-2024)",
160
+ height=1000,
161
+ animation_frame="annee",
162
+ animation_group="code_departement",
163
+ )
164
+ fig.update_traces(marker_line_width=0)
165
+ if fig.layout.updatemenus:
166
+ try:
167
+ fig.layout.updatemenus[0].buttons[0].args[1]["frame"]["duration"] = (
168
+ 1000 # milliseconds per frame
169
+ )
170
+ fig.layout.updatemenus[0].buttons[0].args[1]["transition"]["duration"] = (
171
+ 500 # transition duration
172
+ )
173
+ except IndexError:
174
+ print(
175
+ "Could not set animation speed. updatemenus structure might be unexpected."
176
+ )
177
+ else:
178
+ print(
179
+ "No animation updatemenus found. This usually means 'animation_frame' column has too few unique values or data issues."
180
+ )
181
+
182
+ st.plotly_chart(fig, use_container_width=True)
183
+
184
+ st.write("Hover over the map to see detailed information for each department and year.")
185
+ st.write(
186
+ "Missing values are represented in light grey, while actual data is shown in a gradient from red (high prices) to green (low prices)."
187
+ )
188
+ st.write(
189
+ "Note: The color scale is customized to highlight missing values in light grey, while the actual data is represented using a reversed RdYlGn color scale, where red indicates higher prices and green indicates lower prices."
190
+ )
src/pages/2_Historical_Price_-_Region.py ADDED
@@ -0,0 +1,238 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import streamlit as st
3
+ import os
4
+ import boto3
5
+ import json
6
+ import urllib.request
7
+ import io
8
+ import plotly.colors as pcolors
9
+ import plotly.express as px
10
+
11
+ AWS_S3_BUCKET = os.getenv("AWS_S3_BUCKET", "oasis-prd-001")
12
+ AWS_ACCESS_KEY_ID = os.getenv("AWS_ACCESS_KEY_ID")
13
+ AWS_SECRET_ACCESS_KEY = os.getenv("AWS_SECRET_ACCESS_KEY")
14
+
15
+ # --- Streamlit Page Configuration ---
16
+ st.set_page_config(page_title="Oasis", page_icon=":house:", layout="wide")
17
+
18
+ st.header("Historical Price - Region")
19
+ st.subheader("An overview of real estate prices for each region from 2015 to 2024")
20
+
21
+ st.write(
22
+ "This map shows the average price per square meter for each city over the years, with a focus on climatic events."
23
+ )
24
+
25
+ # --- Data Loading Functions ---
26
+ def load_file_s3(object_key: str) -> pd.DataFrame:
27
+ """Load a file from S3 and return its contents as a pandas DataFrame."""
28
+ if not AWS_S3_BUCKET or not AWS_ACCESS_KEY_ID or not AWS_SECRET_ACCESS_KEY:
29
+ raise ValueError(
30
+ "AWS credentials or bucket name not set in environment variables."
31
+ )
32
+
33
+ s3_client = boto3.client(
34
+ "s3",
35
+ aws_access_key_id=AWS_ACCESS_KEY_ID,
36
+ aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
37
+ )
38
+
39
+ response = s3_client.get_object(Bucket=AWS_S3_BUCKET, Key=object_key)
40
+ status = response.get("ResponseMetadata", {}).get("HTTPStatusCode")
41
+
42
+ if status == 200:
43
+ # Ensure proper decoding and file-like object for pandas
44
+ return pd.read_csv(io.StringIO(response["Body"].read().decode("utf-8")))
45
+ raise ValueError(f"Unsuccessful S3 get_object response. Status - {status}")
46
+
47
+
48
+ @st.cache_data
49
+ def load_geojson():
50
+ """Loads GeoJSON data from a URL and caches it."""
51
+ geojson_url = "https://france-geojson.gregoiredavid.fr/repo/communes.geojson"
52
+ with urllib.request.urlopen(geojson_url) as response:
53
+ communes_geojson = json.load(response)
54
+ return communes_geojson
55
+
56
+
57
+ @st.cache_data
58
+ def load_dataset_housing_prices():
59
+ """Loads the main housing prices dataset from S3 and caches it."""
60
+ df = load_file_s3("processed/housing/dataset_housing_prices.csv")
61
+ return df
62
+
63
+
64
+ @st.cache_data
65
+ def load_dataset_housing_prices_full():
66
+ """Loads the full housing prices dataset from S3 and caches it."""
67
+ df = load_file_s3("processed/housing/dataset_housing_prices_full.csv")
68
+ return df
69
+
70
+
71
+ # --- Data Preprocessing Function (NEW: Cached for efficiency) ---
72
+ @st.cache_data
73
+ def preprocess_housing_data(df_prices, df_full):
74
+ """
75
+ Performs all necessary data preprocessing steps and caches the result.
76
+ This function will only re-run if df_prices or df_full change.
77
+ """
78
+ MISSING_VALUE_PLACEHOLDER = -1
79
+
80
+ # Calculate min/max from the original (non-concatenated) dataset
81
+ min_actual_country_prixm2moyen = df_prices["prixm2moyen"].min()
82
+ max_actual_country_prixm2moyen = df_prices["prixm2moyen"].max()
83
+
84
+ # Identify missing rows from the full dataset
85
+ missing_rows = df_full[
86
+ ~df_full.set_index(["code_commune_insee", "annee"]).index.isin(
87
+ df_prices.set_index(["code_commune_insee", "annee"]).index
88
+ )
89
+ ]
90
+ missing_rows = missing_rows[["code_commune_insee", "annee"]]
91
+ missing_rows["prixm2moyen"] = MISSING_VALUE_PLACEHOLDER
92
+
93
+ # Concatenate and add department code
94
+ processed_df = pd.concat([df_prices, missing_rows], ignore_index=True)
95
+ processed_df["code_departement"] = processed_df["code_commune_insee"].str[:2]
96
+
97
+ return processed_df, min_actual_country_prixm2moyen, max_actual_country_prixm2moyen
98
+
99
+
100
+ # --- Plotly Figure Creation Function (NEW: Cached for efficiency) ---
101
+ @st.cache_data
102
+ def create_animated_choropleth_map(
103
+ filtered_df,
104
+ communes_geojson,
105
+ min_actual_country_prixm2moyen,
106
+ max_actual_country_prixm2moyen,
107
+ ):
108
+ """
109
+ Creates and caches the Plotly choropleth map figure.
110
+ This function will only re-run if filtered_df, communes_geojson,
111
+ or the min/max price values change.
112
+ """
113
+ MISSING_VALUE_PLACEHOLDER = -1 # Needs to be consistent with preprocessing
114
+
115
+ color_range_min = MISSING_VALUE_PLACEHOLDER
116
+ color_range_max = max_actual_country_prixm2moyen
117
+
118
+ # Normalize the actual min/max to a 0-1 scale for defining the custom colorscale points
119
+ normalized_min_actual = (min_actual_country_prixm2moyen - color_range_min) / (
120
+ color_range_max - color_range_min
121
+ )
122
+ normalized_max_actual = (max_actual_country_prixm2moyen - color_range_min) / (
123
+ color_range_max - color_range_min
124
+ )
125
+
126
+ custom_colorscale = []
127
+ # Add the color for missing values
128
+ custom_colorscale.append([0.0, "lightgrey"])
129
+ reversed_rdylgn_colors = pcolors.diverging.RdYlGn[::-1]
130
+ # Add the reversed RdYlGn colors for the actual data range
131
+ num_steps = len(reversed_rdylgn_colors)
132
+ for i, color in enumerate(reversed_rdylgn_colors):
133
+ normalized_point = normalized_min_actual + (
134
+ normalized_max_actual - normalized_min_actual
135
+ ) * (i / (num_steps - 1))
136
+ if normalized_point > 0.0: # Ensure we don't overwrite the grey for missing
137
+ custom_colorscale.append([normalized_point, color])
138
+
139
+ # Sort the custom_colorscale by the normalized value to ensure correct order
140
+ custom_colorscale = sorted(custom_colorscale, key=lambda x: x[0])
141
+
142
+ fig = px.choropleth_map(
143
+ filtered_df,
144
+ geojson=communes_geojson,
145
+ locations="code_commune_insee",
146
+ featureidkey="properties.code",
147
+ color="prixm2moyen",
148
+ range_color=[min_actual_country_prixm2moyen, max_actual_country_prixm2moyen],
149
+ color_continuous_scale=custom_colorscale,
150
+ center={"lat": 46.6, "lon": 2.6},
151
+ zoom=5,
152
+ opacity=0.75,
153
+ hover_name="code_commune_insee",
154
+ hover_data={
155
+ "prixm2moyen": ":.0f",
156
+ "annee": True, # Include year in hover data
157
+ },
158
+ title="Average Price per Square Meter in French Communes (2015-2024)",
159
+ height=800,
160
+ animation_frame="annee",
161
+ animation_group="code_commune_insee",
162
+ )
163
+ fig.update_traces(marker_line_width=0)
164
+
165
+ # Set animation speed (error handling for robustness)
166
+ if fig.layout.updatemenus:
167
+ try:
168
+ fig.layout.updatemenus[0].buttons[0].args[1]["frame"]["duration"] = 1500
169
+ fig.layout.updatemenus[0].buttons[0].args[1]["transition"]["duration"] = 500
170
+ except IndexError:
171
+ st.warning(
172
+ "Could not set animation speed. Updatemenus structure might be unexpected."
173
+ )
174
+ else:
175
+ st.warning(
176
+ "No animation updatemenus found. This usually means 'animation_frame' column has too few unique values or data issues."
177
+ )
178
+ return fig
179
+
180
+
181
+ #####################################################################
182
+ # Main Streamlit App Logic
183
+ #####################################################################
184
+
185
+ # Use st.spinner for initial loading and preprocessing
186
+ with st.spinner("Loading and preprocessing data... This might take a moment."):
187
+ # Load the raw datasets (these are cached)
188
+ dataset_housing_prices = load_dataset_housing_prices()
189
+ dataset_housing_prices_full = load_dataset_housing_prices_full()
190
+ communes_geojson = load_geojson()
191
+
192
+ # Preprocess the data (this result is cached)
193
+ (
194
+ processed_housing_data,
195
+ min_actual_country_prixm2moyen,
196
+ max_actual_country_prixm2moyen,
197
+ ) = preprocess_housing_data(dataset_housing_prices, dataset_housing_prices_full)
198
+
199
+ # Dropdown for department selection (this interaction triggers a rerun)
200
+ st.subheader("Select a Department to View Commune Prices")
201
+ selected_departement = st.selectbox(
202
+ "Select a Department",
203
+ options=processed_housing_data["code_departement"].unique(),
204
+ )
205
+
206
+ # Filter data based on selected department (this happens on every rerun after selection)
207
+ # This filtering is fast on the already preprocessed data.
208
+ filtered_data_for_map = processed_housing_data[
209
+ processed_housing_data["code_departement"] == selected_departement
210
+ ].copy() # Use .copy() to avoid SettingWithCopyWarning
211
+ filtered_data_for_map = filtered_data_for_map[
212
+ ["code_commune_insee", "annee", "prixm2moyen"]
213
+ ]
214
+
215
+
216
+ # Create and display the choropleth map (this result is cached based on filtered_data_for_map)
217
+ fig = create_animated_choropleth_map(
218
+ filtered_data_for_map,
219
+ communes_geojson,
220
+ min_actual_country_prixm2moyen,
221
+ max_actual_country_prixm2moyen,
222
+ )
223
+
224
+ st.subheader("Average Price per Square Meter in French Communes (2015-2024)")
225
+ st.write(
226
+ "This map shows the average price per square meter in French communes over the years, with a focus on climatic events."
227
+ )
228
+ st.plotly_chart(fig, use_container_width=True)
229
+ st.write("Hover over the map to see detailed information for each commune and year.")
230
+ st.write(
231
+ "Missing values are represented in light grey, while actual data is shown in a gradient from red (high prices) to green (low prices)."
232
+ )
233
+ st.write(
234
+ "Note: The color scale is customized to highlight missing values in light grey, while the actual data is represented using a reversed RdYlGn color scale, where red indicates higher prices and green indicates lower prices."
235
+ )
236
+ st.write(
237
+ "The map is animated by year, allowing you to see how the average price per square meter changes over time."
238
+ )
src/pages/3_About_Us.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Create an about page to present the team and the project in more detail
2
+ # The team includes:
3
+ # - Frederic, the project manager
4
+ # - Olivior, the data scientist
5
+ # - Nick, the developer
6
+ # - Faycel, the data engineer
7
+ # - Francis, the data analyst
8
+
9
+ import streamlit as st
10
+
11
+ st.set_page_config(page_title="About Us", page_icon="ℹ️", layout="centered")
12
+ st.title("ℹ️ About Us")
13
+ st.write(
14
+ """
15
+ This ambitious project, Oasis, aims to predict real estate prices with a primary focus on the impact of climatic events. Our goal is to identify safe and profitable locations by analyzing how various weather and climate patterns influence property values.
16
+
17
+ ## The Team
18
+ - Frederic, the project manager
19
+ - Olivior, the data scientist
20
+ - Nick, the developer
21
+ - Faycel, the data engineer
22
+ - Francis, the data analyst
23
+ """
24
+ )