-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Also, a change to the _place_data template that means that datasets with no actual data in them do not render empty cards
- Loading branch information
1 parent
4d1d857
commit 007d383
Showing
2 changed files
with
248 additions
and
120 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
from django.conf import settings | ||
from django.core.management.base import BaseCommand | ||
|
||
import pandas as pd | ||
from tqdm import tqdm | ||
|
||
from hub.models import Area, AreaData, DataSet, DataType | ||
|
||
|
||
class Command(BaseCommand): | ||
help = "Import flood risk data" | ||
|
||
data_file = settings.BASE_DIR / "data" / "risk_of_flooding.csv" | ||
message = "Importing constituency flood risk data" | ||
defaults = { | ||
"label": "Flood risk from rivers or sea", | ||
"description": "Data relating to risk of flooding from rivers or sea, recorded in 2018", | ||
"data_type": "percent", | ||
"category": "place", | ||
"source_label": "Defra", | ||
"is_range": True, | ||
"source_type": "csv", | ||
"table": "areadata", | ||
"exclude_countries": ["Northern Ireland", "Scotland", "Wales"], | ||
"comparators": DataSet.numerical_comparators(), | ||
"default_value": 10, | ||
"is_shadable": False, | ||
} | ||
|
||
def add_arguments(self, parser): | ||
parser.add_argument( | ||
"-q", "--quiet", action="store_true", help="Silence progress bars." | ||
) | ||
|
||
def handle(self, quiet=False, *args, **options): | ||
self._quiet = quiet | ||
df = self.get_dataframe() | ||
self.data_types = self.create_data_types(df) | ||
self.delete_data() | ||
self.import_data(df) | ||
|
||
def create_data_types(self, df): | ||
if not self._quiet: | ||
self.stdout.write("Creating dataset + types") | ||
data_set, created = DataSet.objects.update_or_create( | ||
name="constituency_flood_risk", defaults=self.defaults | ||
) | ||
data_types = [] | ||
for col in tqdm(df.columns, disable=self._quiet): | ||
data_type, created = DataType.objects.update_or_create( | ||
data_set=data_set, | ||
name=f"flood_risk_{col.lower().replace(' ', '_')}", | ||
defaults={ | ||
"data_type": "percent", | ||
"label": col, | ||
"description": f"Percentage of the constituency marked as '{col}' risk of flooding from rivers or the sea", | ||
}, | ||
) | ||
data_types.append(data_type) | ||
|
||
return data_types | ||
|
||
def import_data(self, df): | ||
if not self._quiet: | ||
self.stdout.write("Importing flood risk data") | ||
for gss, row in tqdm(df.iterrows(), disable=self._quiet): | ||
try: | ||
area = Area.objects.get(gss=gss) | ||
except Area.DoesNotExist: | ||
self.stdout.write(f"Failed to find area with code {gss}") | ||
continue | ||
for data_type in self.data_types: | ||
AreaData.objects.create( | ||
data_type=data_type, | ||
area=area, | ||
data=row[data_type.label], | ||
) | ||
for col in df.columns: | ||
average = df[col].mean() | ||
data_type = DataType.objects.get( | ||
name=f"flood_risk_{col.lower().replace(' ', '_')}" | ||
) | ||
data_type.average = average | ||
data_type.save() | ||
|
||
def delete_data(self): | ||
AreaData.objects.filter(data_type__in=self.data_types).delete() | ||
|
||
def get_dataframe(self): | ||
df = pd.read_csv(self.data_file) | ||
totals = ( | ||
df.dropna()[["gss", "prob_4band"]] | ||
.groupby("gss") | ||
.count() | ||
.prob_4band.to_dict() | ||
) | ||
df["total"] = df.gss.apply(lambda x: totals.get(x, None)) | ||
df = ( | ||
df.dropna()[["gss", "prob_4band", "total"]] | ||
.groupby("gss") | ||
.value_counts() | ||
.reset_index() | ||
.rename(columns={0: "value"}) | ||
) | ||
df["percentage"] = df.value / df.total * 100 | ||
df = df.pivot(columns="prob_4band", values="percentage", index="gss").fillna(0) | ||
return df |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,131 +1,152 @@ | ||
{% load humanize %} | ||
{% load hub_filters %} | ||
|
||
<div class="col d-flex flex-column"> | ||
<div class="card flex-grow-1 dataset-card {% if category.is_favourite or category.data.is_favourite %}dataset-card--favourite{% endif %}"> | ||
<div class="dataset-card-header"> | ||
<h5>{{ category.name }}</h5> | ||
{% include "hub/area/_favourite.html" %} | ||
</div> | ||
<div class="card-body"> | ||
{% if category.name == "Constituency age distribution" %} | ||
<table class="table mb-0 js-chart" data-chart-type="bar" data-chart-direction="y"> | ||
<thead> | ||
<tr> | ||
<th scope="col">Age band</th> | ||
<th scope="col" data-color="#068670">This area</th> | ||
<th scope="col" data-color="#ced4da">UK average</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{% for range in category.data %} | ||
<tr> | ||
<th>{{ range.label }}</th> | ||
<td>{{ range.value|floatformat }}{% if range.is_percentage %}%{% endif %}</td> | ||
<td>{{ range.average|floatformat }}{% if range.is_percentage %}%{% endif %}</td> | ||
</tr> | ||
{% endfor %} | ||
</tbody> | ||
</table> | ||
{% elif category.name == "Ethnicity" %} | ||
<table class="table mb-0"> | ||
<thead> | ||
<tr> | ||
<th scope="col">Ethnicity</th> | ||
<th scope="col" data-color="#068670">This area</th> | ||
<th scope="col" data-color="#ced4da">UK average</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{% for range in category.data %} | ||
<tr> | ||
<th>{{ range.label }}</th> | ||
<td>{{ range.value|floatformat }}{% if range.is_percentage %}%{% endif %}</td> | ||
<td>{{ range.average|floatformat }}{% if range.is_percentage %}%{% endif %}</td> | ||
</tr> | ||
{% endfor %} | ||
</tbody> | ||
</table> | ||
{% elif category.name == 'Socio-Economic Status' %} | ||
<table class="table mb-0"> | ||
<thead> | ||
<tr> | ||
<th scope="col">Status</th> | ||
<th scope="col">This area</th> | ||
<th scope="col" class="text-muted">UK average</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{% for range in category.data %} | ||
<tr> | ||
<th>{{ range.label }}</th> | ||
<td>{{ range.value|floatformat }}%</td> | ||
<td class="text-muted">{{ range.average|floatformat }}%</td> | ||
</tr> | ||
{% endfor %} | ||
</tbody> | ||
</table> | ||
{% elif category.data.data_type.name == 'constituency_popular_petitions' %} | ||
<table class="table mb-0"> | ||
<thead> | ||
<tr> | ||
<th scope="col">Petition</th> | ||
<th scope="col">Signatures</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{% for petition in category.data.value %} | ||
<tr> | ||
<td><a href="{{ petition.url }}">{{ petition.action }}</a></td> | ||
<td class="text-end">{{ petition.signatures }}</td> | ||
</tr> | ||
{% endfor %} | ||
</body> | ||
</table> | ||
{% elif category.name == 'Air pollution' %} | ||
<table class="table mb-0"> | ||
<thead> | ||
<tr> | ||
<th scope="col">Pollutant</th> | ||
<th scope="col">This area</th> | ||
<th scope="col" class="text-muted">UK average</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{% if category.data %} | ||
<div class="col d-flex flex-column"> | ||
<div class="card flex-grow-1 dataset-card {% if category.is_favourite or category.data.is_favourite %}dataset-card--favourite{% endif %}"> | ||
<div class="dataset-card-header"> | ||
<h5>{{ category.name }}</h5> | ||
{% include "hub/area/_favourite.html" %} | ||
</div> | ||
<div class="card-body"> | ||
{% if category.name == "Constituency age distribution" %} | ||
<table class="table mb-0 js-chart" data-chart-type="bar" data-chart-direction="y"> | ||
<thead> | ||
<tr> | ||
<th scope="col">Age band</th> | ||
<th scope="col" data-color="#068670">This area</th> | ||
<th scope="col" data-color="#ced4da">UK average</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{% for range in category.data %} | ||
<tr> | ||
<th> | ||
{{ range.label|html_format_dataset_name|safe }} | ||
{% if range.data_type.description %} | ||
<small class="d-block mt-1 fs-8 fw-normal">{{ range.data_type.description|escape }}</small> | ||
{% endif %} | ||
</th> | ||
<td>{{ range.value|floatformat }}%</td> | ||
<td class="text-muted">{{ range.average|floatformat }}%</td> | ||
</tr> | ||
<tr> | ||
<th>{{ range.label }}</th> | ||
<td>{{ range.value|floatformat }}{% if range.is_percentage %}%{% endif %}</td> | ||
<td>{{ range.average|floatformat }}{% if range.is_percentage %}%{% endif %}</td> | ||
</tr> | ||
{% endfor %} | ||
</tbody> | ||
</table> | ||
{% else %} | ||
{% if category.data.is_number %} | ||
<p class="card-text mb-0 display-6 lh-1 text-primary">{{ category.data.value|floatformat }}{% if category.data.is_percentage %}%{% endif %}</p> | ||
{% if category.data.average %} | ||
<p class="card-text mt-2 text-muted">{{ category.data.average|floatformat }}{% if category.data.is_percentage %}%{% endif %} national average</p> | ||
{% endif %} | ||
</tbody> | ||
</table> | ||
{% elif category.name == "Ethnicity" %} | ||
<table class="table mb-0"> | ||
<thead> | ||
<tr> | ||
<th scope="col">Ethnicity</th> | ||
<th scope="col" data-color="#068670">This area</th> | ||
<th scope="col" data-color="#ced4da">UK average</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{% for range in category.data %} | ||
<tr> | ||
<th>{{ range.label }}</th> | ||
<td>{{ range.value|floatformat }}{% if range.is_percentage %}%{% endif %}</td> | ||
<td>{{ range.average|floatformat }}{% if range.is_percentage %}%{% endif %}</td> | ||
</tr> | ||
{% endfor %} | ||
</tbody> | ||
</table> | ||
{% elif category.name == 'Socio-Economic Status' %} | ||
<table class="table mb-0"> | ||
<thead> | ||
<tr> | ||
<th scope="col">Status</th> | ||
<th scope="col">This area</th> | ||
<th scope="col" class="text-muted">UK average</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{% for range in category.data %} | ||
<tr> | ||
<th>{{ range.label }}</th> | ||
<td>{{ range.value|floatformat }}%</td> | ||
<td class="text-muted">{{ range.average|floatformat }}%</td> | ||
</tr> | ||
{% endfor %} | ||
</tbody> | ||
</table> | ||
{% elif category.data.data_type.name == 'constituency_popular_petitions' %} | ||
<table class="table mb-0"> | ||
<thead> | ||
<tr> | ||
<th scope="col">Petition</th> | ||
<th scope="col">Signatures</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{% for petition in category.data.value %} | ||
<tr> | ||
<td><a href="{{ petition.url }}">{{ petition.action }}</a></td> | ||
<td class="text-end">{{ petition.signatures }}</td> | ||
</tr> | ||
{% endfor %} | ||
</body> | ||
</table> | ||
{% elif category.name == 'Air pollution' %} | ||
<table class="table mb-0"> | ||
<thead> | ||
<tr> | ||
<th scope="col">Pollutant</th> | ||
<th scope="col">This area</th> | ||
<th scope="col" class="text-muted">UK average</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{% for range in category.data %} | ||
<tr> | ||
<th> | ||
{{ range.label|html_format_dataset_name|safe }} | ||
{% if range.data_type.description %} | ||
<small class="d-block mt-1 fs-8 fw-normal">{{ range.data_type.description|escape }}</small> | ||
{% endif %} | ||
</th> | ||
<td>{{ range.value|floatformat }}%</td> | ||
<td class="text-muted">{{ range.average|floatformat }}%</td> | ||
</tr> | ||
{% endfor %} | ||
</tbody> | ||
</table> | ||
{% elif category.name == 'Flood risk from rivers or sea' %} | ||
<table class="table mb-0 js-chart" data-chart-type="bar" data-chart-direction="y""> | ||
<thead> | ||
<tr> | ||
<th scope="col">Risk</th> | ||
<th scope="col" data-color="#068670">This area</th> | ||
<th scope="col" data-color="#ced4da">UK average</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{% for range in category.data %} | ||
<tr> | ||
<th>{{ range.label }}</th> | ||
<td>{{ range.value|floatformat }}%</td> | ||
<td class="text-muted">{{ range.average|floatformat }}%</td> | ||
</tr> | ||
{% endfor %} | ||
</tbody> | ||
</table> | ||
{% else %} | ||
<p class="card-text fs-5">{{ category.data.value }}</p> | ||
{% if category.data.average %} | ||
<p class="card-text mt-2 text-muted">{{ category.data.average }} national average</p> | ||
{% if category.data.is_number %} | ||
<p class="card-text mb-0 display-6 lh-1 text-primary">{{ category.data.value|floatformat }}{% if category.data.is_percentage %}%{% endif %}</p> | ||
{% if category.data.average %} | ||
<p class="card-text mt-2 text-muted">{{ category.data.average|floatformat }}{% if category.data.is_percentage %}%{% endif %} national average</p> | ||
{% endif %} | ||
{% else %} | ||
<p class="card-text fs-5">{{ category.data.value }}</p> | ||
{% if category.data.average %} | ||
<p class="card-text mt-2 text-muted">{{ category.data.average }} national average</p> | ||
{% endif %} | ||
{% endif %} | ||
{% endif %} | ||
</div> | ||
<div class="card-footer bg-white"> | ||
{% if category.source_url %} | ||
<p class="card-text fs-8"><a href="{{ category.source_url }}" class="text-decoration-none text-muted">Data from {{ category.source }}</a></p> | ||
{% else %} | ||
<p class="card-text fs-8 text-muted">Data from {{ category.source }}</p> | ||
{% endif %} | ||
</div> | ||
<div class="card-footer bg-white"> | ||
{% if category.source_url %} | ||
<p class="card-text fs-8"><a href="{{ category.source_url }}" class="text-decoration-none text-muted">Data from {{ category.source }}</a></p> | ||
{% else %} | ||
<p class="card-text fs-8 text-muted">Data from {{ category.source }}</p> | ||
{% endif %} | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
{% endif %} |