Skip to content

Commit

Permalink
Merge pull request #36 from SocialFinanceDigitalLabs/22-create-histor…
Browse files Browse the repository at this point in the history
…ic-view

22 create historic view
  • Loading branch information
amynickolls committed Apr 15, 2024
2 parents 4840910 + d03092a commit 8ec484f
Show file tree
Hide file tree
Showing 11 changed files with 152 additions and 21 deletions.
24 changes: 24 additions & 0 deletions dm_regional_app/charts.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import plotly.express as px
from demand_model.multinomial.predictor import Prediction

from ssda903.datacontainer import DemandModellingDataContainer
from ssda903.population_stats import PopulationStats


def prediction_chart(prediction: Prediction):
df_pp = prediction.population.unstack().reset_index()
Expand All @@ -11,3 +14,24 @@ def prediction_chart(prediction: Prediction):
fig.update_layout(title="Prediction")
fig_html = fig.to_html(full_html=False)
return fig_html


def historic_chart(data: PopulationStats):
df_pp = data.stock.unstack().reset_index()
df_pp.columns = ["from", "date", "value"]
df = df_pp[["date", "value"]].groupby(by="date").sum().reset_index()

# visualise prediction using unstacked dataframe
fig = px.line(
df,
y="value",
x="date",
labels={
"value": "Number of children",
"date": "Date",
},
)
fig.update_layout(title="Historic data")
fig.update_yaxes(rangemode="tozero")
fig_html = fig.to_html(full_html=False)
return fig_html
60 changes: 50 additions & 10 deletions dm_regional_app/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Column, Layout, Row, Submit
from django import forms
from django_select2 import forms as s2forms


class PredictFilter(forms.Form):
Expand All @@ -29,35 +30,58 @@ class HistoricDataFilter(forms.Form):
label="End Date",
required=True,
)
la = forms.ChoiceField(label="Local Authority", required=False, choices=[])
la = forms.MultipleChoiceField(
widget=s2forms.Select2MultipleWidget,
label="Local Authority",
required=False,
choices=[],
)
placement_types = forms.MultipleChoiceField(
widget=forms.CheckboxSelectMultiple,
widget=s2forms.Select2MultipleWidget,
label="Placement Type",
required=False,
choices=[],
)
age_bins = forms.MultipleChoiceField(
widget=s2forms.Select2MultipleWidget,
label="Age",
required=False,
choices=[],
)
uasc = forms.ChoiceField(
label="UASC",
required=False,
choices=[("all", "All"), (True, "UASC only"), (False, "Exclude UASC")],
initial="all",
)

def __init__(self, *args, **kwargs):
la_choices = kwargs.pop("la_choices")
placement_type_choices = kwargs.pop("placement_type_choices")
age_bin_choices = kwargs.pop("age_bin_choices")

super().__init__(*args, **kwargs)
self.fields["la"].choices = [("all", "All")] + [(la, la) for la in la_choices]
self.fields["la"].initial = "all"
self.fields["la"].choices = [(la, la) for la in la_choices]
self.fields["placement_types"].choices = [
(placement_type, placement_type)
for placement_type in placement_type_choices
]
self.fields["age_bins"].choices = [
(age_bin, age_bin) for age_bin in age_bin_choices
]

self.helper = FormHelper()
self.helper.layout = Layout(
Row(
Column("start_date", css_class="form-group col-md-6 mb-0"),
Column("end_date", css_class="form-group col-md-6 mb-0"),
Column("start_date", css_class="form-group col-md-3 mb-0"),
Column("end_date", css_class="form-group col-md-3 mb-0"),
css_class="form-row",
),
Row(
Column("la", css_class="form-group col-md-6 mb-0"),
Column("placement_types", css_class="form-group col-md-4 mb-0"),
Column("la", css_class="form-group col-md-3 mb-0"),
Column("placement_types", css_class="form-group col-md-3 mb-0"),
Column("age_bins", css_class="form-group col-md-3 mb-0"),
Column("uasc", css_class="form-group col-md-3 mb-0"),
css_class="form-row",
),
Submit("submit", "Filter"),
Expand All @@ -72,8 +96,9 @@ def filter_by_end_date(self, data: pd.DataFrame):
return data

def filter_by_la(self, data: pd.DataFrame):
if self.cleaned_data["la"] != "all":
data = data.loc[data.LA == self.cleaned_data["la"]]
if self.cleaned_data["la"] != []:
loc = data.LA.astype(str).isin(self.cleaned_data["la"])
data = data.loc[loc]
return data

def filter_by_placement_type(self, data: pd.DataFrame):
Expand All @@ -84,9 +109,24 @@ def filter_by_placement_type(self, data: pd.DataFrame):
data = data.loc[loc]
return data

def filter_by_age_bin(self, data: pd.DataFrame):
if self.cleaned_data["age_bins"] != []:
loc = data.age_bin.astype(str).isin(self.cleaned_data["age_bins"])
data = data.loc[loc]
return data

def filter_by_uasc(self, data: pd.DataFrame):
if self.cleaned_data["uasc"] == "True":
data = data.loc[data.UASC == True]
elif self.cleaned_data["uasc"] == "False":
data = data.loc[data.UASC == True]
return data

def apply_filters(self, data: pd.DataFrame):
data = self.filter_by_start_date(data)
data = self.filter_by_end_date(data)
data = self.filter_by_la(data)
data = self.filter_by_placement_type(data)
data = self.filter_by_age_bin(data)
data = self.filter_by_uasc(data)
return data
10 changes: 9 additions & 1 deletion dm_regional_app/management/commands/sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,16 @@

class Command(BaseCommand):
def handle(self, *args, **kwargs):
"""thing = User.objects.all()
for t in thing:
print(t.profile.la)"""
datastore = StorageDataStore(default_storage, settings.DATA_SOURCE)
config = Config()
dc = DemandModellingDataContainer(datastore, config)
pop = PopulationStats(dc.enriched_view, config)

print(dc.enriched_view)
data = dc.enriched_view
print(data.loc[data.UASC == True])

# print(pop.stock)
# print(dc.enriched_view)
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
{% if user.is_authenticated %}
{% include 'dm_regional_app/includes/navbar-item.html' with url='dashboard' name='Dashboard' url_names="['dashboard']" %}
{% include 'dm_regional_app/includes/navbar-item.html' with url='historic_data' name='Historic Data' url_names="['historic_data']" %}
{% include 'dm_regional_app/includes/navbar-item.html' with url='prediction' name='Prediction' url_names="['prediction']" %}
{% include 'dm_regional_app/includes/navbar-item.html' with url='scenarios' name='Scenarios' url_names="['scenarios', 'scenario_detail']" %}
{% else %}
Expand Down
17 changes: 13 additions & 4 deletions dm_regional_app/templates/dm_regional_app/views/historic.html
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
{% extends "dm_regional_app/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<br>
<h1>Viewing the SSDA903 data</h1>
<br>
<p>Explore the make-up of the care population as represented in the SSDA903 returns. You can filter the data to see the pattern of new care episodes over time across dimensions such as placement type, provider type, child age, child ethnicity, child gender, and whether the new placement followed a breakdown.</p>
<br>
<p><i>Select the period of data in the SSDA903 dataset you want to explore</i></p>
{{ form.media }}
<form method="POST">
{% csrf_token %}
{% crispy form %}
</form>
<br>
<div class="row">
<div class="col">
<h1>Total # of entry into care</h1>
<p>{{ entry_into_care_count }}</p>
<p><h1 class="display-2">{{ entry_into_care_count }}</h1></p>
</div>
<div class="col">
<h1>Total # of exiting care</h1>
<p>{{ exiting_care_count }}</p>
<p><h1 class="display-2">{{ exiting_care_count }}</h1></p>
</div>
</div>
<div class="alert alert-primary" role="alert">
TODO - add the relevant charts here!

{{chart|safe}}
</div>
<div class="d-flex bd-highlight mb-3">
<div class="ms-auto p-2 bd-highlight"><a class="btn btn-primary" href="{% url 'prediction' %}">Next</a></div>
</div>
{% endblock %}
1 change: 1 addition & 0 deletions dm_regional_app/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@
path("predict/", views.prediction, name="prediction"),
path("scenario/<int:pk>", views.scenario_detail, name="scenario_detail"),
path("scenarios", views.scenarios, name="scenarios"),
path("historic_data", views.historic_data, name="historic_data"),
]
13 changes: 11 additions & 2 deletions dm_regional_app/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
from django.contrib.auth.decorators import login_required
from django.shortcuts import get_object_or_404, render

from dm_regional_app.charts import prediction_chart
from dm_regional_app.charts import historic_chart, prediction_chart
from dm_regional_app.forms import HistoricDataFilter, PredictFilter
from dm_regional_app.models import Scenario
from ssda903 import Config
from ssda903.population_stats import PopulationStats
from ssda903.predictor import predict
from ssda903.reader import read_data

Expand Down Expand Up @@ -78,6 +80,7 @@ def historic_data(request):
request.POST,
la_choices=datacontainer.unique_las,
placement_type_choices=datacontainer.unique_placement_types,
age_bin_choices=datacontainer.unique_age_bins,
)
if form.is_valid():
data = form.apply_filters(datacontainer.enriched_view)
Expand All @@ -95,10 +98,10 @@ def historic_data(request):
},
la_choices=datacontainer.unique_las,
placement_type_choices=datacontainer.unique_placement_types,
age_bin_choices=datacontainer.unique_age_bins,
)
data = datacontainer.enriched_view

# TODO AMY - create the relevant charts from this dataset, according to the designs
entry_into_care_count = data.loc[
data.placement_type_before
== datacontainer.config.PlacementCategories.NOT_IN_CARE
Expand All @@ -108,13 +111,19 @@ def historic_data(request):
== datacontainer.config.PlacementCategories.NOT_IN_CARE
]["CHILD"].nunique()

config = Config()
stats = PopulationStats(data, config)

chart = historic_chart(stats)

return render(
request,
"dm_regional_app/views/historic.html",
{
"form": form,
"entry_into_care_count": entry_into_care_count,
"exiting_care_count": exiting_care_count,
"chart": chart,
},
)

Expand Down
1 change: 1 addition & 0 deletions dm_regional_site/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"crispy_forms",
"crispy_bootstrap5",
"bootstrap_datepicker_plus",
"django_select2",
]

MIDDLEWARE = [
Expand Down
38 changes: 36 additions & 2 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ crispy-bootstrap5 = "^2024.2"
python-decouple = "^3.8"
faker = "^23.3.0"
django-bootstrap-datepicker-plus = "^5.0.5"
django-select2 = "^8.1.2"


[tool.poetry.group.dev.dependencies]
Expand Down
7 changes: 5 additions & 2 deletions ssda903/datacontainer.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from functools import cached_property
from typing import Optional


import numpy as np
import pandas as pd

Expand Down Expand Up @@ -117,7 +116,7 @@ def combined_datasets(self) -> pd.DataFrame:
merged = merged.merge(uasc[["CHILD", "DUC"]], how="left", on="CHILD")

# create UASC flag if DECOM is less than DUC
merged["UASC"] = np.where(merged["DECOM"] < merged["DUC"], 1, 0)
merged["UASC"] = np.where(merged["DECOM"] < merged["DUC"], True, False)

return merged

Expand Down Expand Up @@ -203,6 +202,10 @@ def unique_las(self) -> pd.Series:
def unique_placement_types(self) -> pd.Series:
return self.enriched_view.placement_type.unique()

@cached_property
def unique_age_bins(self) -> pd.Series:
return self.enriched_view.age_bin.unique()

def _add_ages(self, combined: pd.DataFrame) -> pd.DataFrame:
"""
Calculates the age of the child at the start and end of the episode and adds them as columns
Expand Down

0 comments on commit 8ec484f

Please sign in to comment.