The Profile Page

You need to tell Wazimap what data to show on a place’s profile page, and how to show it.

The Profile Builder Function

When Wazimap builds the profile page for a place, it calls the function defined the profile_builder configuration setting. This function must return a specially-formatted dict-like structure that contains all the information that will be shown on the profile page.

Create a profiles.py file in your project folder and add a function get_profile like in the example below. Remember to set the profile_builder setting in your settings.py to the dotted path to this function, for example:

WAZIMAP['profile_builder'] = 'wazimap_ex.profiles.get_profile'

Here’s a simple example of a profile builder:

from wazimap.data.utils import get_session, get_stat_data

def get_profile(geo_code, geo_level, profile_name=None):
    # get a SQLAlchemy database session
    session = get_session()
    data = {}

    try:
        data['demographics'] = get_demographics_profile(geo_code, geo_level, session)
        # ... load other sections here

        return data
    finally:
        # tidy up the session
        session.close()

def get_demographics_profile(geo_code, geo_level, session)
    # get the number of people for each sex
    sex_dist_data, total_pop = get_stat_data('sex', geo_level, geo_code, session)
    return {
        'sex_distribution': sex_dist_data,
    }

In get_profile we simply get a database session and then call get_demographics_profile to do the heavy lifting.

In get_demographics_profile we use the get_stat_data function to get data on the place’s population for the field sex from a relevant Field Table. It does all the work of formatting the results correctly, and we return it under the key sex_distribution.

All that data is then passed into the Profile Page template where you choose how to show the data.

Get Stat Data for Field Tables

The get_stat_data function is powerful and flexible. It finds the appropriate Field Table for the fields you want, fetches data, remaps fields if you need it, calculates percentages if necessary, and adds the metadata required by the profile page.

utils.get_stat_data(fields, geo_level, geo_code, session, order_by=None, percent=True, total=None, table_fields=None, table_name=None, only=None, exclude=None, exclude_zero=False, recode=None, key_order=None, table_dataset=None, percent_grouping=None, slices=None)

This is our primary helper routine for building a dictionary suitable for a place’s profile page, based on a statistic.

It sums over the data for fields in the database for the place identified by geo_level and geo_code and calculates numerators and values. If multiple fields are given, it creates nested result dictionaries.

Control the rows that are included or ignored using only, exclude and exclude_zero.

The field values can be recoded using recode and and re-ordered using key_order.

Parameters:
  • fields (str or list) – the census field to build stats for. Specify a list of fields to build nested statistics. If multiple fields are specified, then the values of parameters such as only, exclude and recode will change. These must be fields in api.models.census.census_fields, e.g. ‘highest educational level’
  • geo_level (str) – the geographical level
  • geo_code (str) – the geographical code
  • session (dbsession) – sqlalchemy session
  • order_by (str) – field to order by, or None for default, eg. ‘-total’
  • percent (bool) – should we calculate percentages, or just sum raw values?
  • percent_grouping (list) – when calculating percentages, which fields should rows be grouped by? Default: none of them – calculate each entry as a percentage of the whole dataset. Ignored unless percent is True.
  • table_fields (list) – list of fields to use to find the table, defaults to fields
  • total (int) – the total value to use for percentages, or None to total columns automatically
  • table_name (str) – override the table name, otherwise it’s calculated from the fields and geo_level
  • only (dict or list) – only include these field values. If fields has many items, this must be a dict mapping field names to a list of strings.
  • exclude (dict or list) – ignore these field values. If fields has many items, this must be a dict mapping field names to a list of strings. Field names are checked before any recoding.
  • exclude_zero (bool) – ignore fields that have a zero or null total
  • recode (dict or lambda) – function or dict to recode values of key_field. If fields is a singleton, then the keys of this dict must be the values to recode from, otherwise they must be the field names and then the values. If this is a lambda, it is called with the field name and its value as arguments.
  • key_order (dict or list) – ordering for keys in result dictionary. If fields has many items, this must be a dict from field names to orderings. The default ordering is determined by order.
  • table_dataset (str) – dataset used to help find the table if table_name isn’t given.
  • slices (list) – return only a slice of the final data, by choosing a single value for each field in the field list, as specified in the slice list.
Returns:

(data-dictionary, total)

Get Stat Data for Simple Tables

If you’re working with Simple Tables, you’ll want to use get_datatable and SimpleTable.get_stat_data.

SimpleTable.get_stat_data(geo_level, geo_code, fields=None, key_order=None, percent=True, total=None, recode=None)[source]

Get a data dictionary for a place from this table.

This fetches the values for each column in this table and returns a data dictionary for those values, with appropriate names and metadata.

Parameters:
  • geo_level (str) – the geographical level
  • geo_code (str) – the geographical code
  • or list fields (str) – the columns to fetch stats for. By default, all columns except geo-related and the total column (if any) are used.
  • key_order (str) – explicit ordering of (recoded) keys, or None for the default order. Default order is the order in +fields+ if given, otherwise it’s the natural column order from the DB.
  • percent (bool) – should we calculate percentages, or just include raw values?
  • total (int) – the total value to use for percentages, name of a field, or None to use the sum of all retrieved fields (default)
  • recode (dict) – map from field names to strings to recode column names. Many fields can be recoded to the same thing, their values will be summed.
Returns:

(data-dictionary, total)

The Profile Page Template

You need to tell Wazimap how to display your stats on a place’s profile page.

The file you want to override is templates/profile/profile_detail.html, you can see what it looks like in the repo. You generally only need to change the profile_detail block.

Create a new file in your project called templates/profile/profile_detail.html that extends the existing template and provides your new content for the profile_detail block:

{% extends 'profile/profile_detail.html' %}

{% block profile_detail %}
... your stats go here ...
{% endblock %}

If you reload your site you’ll see the homepage has your new content. Django uses this template instead of Wazimap’s version, relying on Wazimap for the blocks you don’t override. There are lots of other blocks you can change, take a look at the original file for more ideas.

See also

There’s more information on changing Wazimap templates in Customising Wazimap.

You must still provide the content that goes into each stats block. The easiest right now is to see how other countries do it, such as South Africa’s default census profile.

Profile Page Charts

The Django template for the profile page creates empty slots for each chart, which are filled by Javascript when the page loads. These placeholders look something like this:

<div class="column-half" id="chart-histogram-demographics-age-distribution" data-stat-type="scaled-percentage" data-chart-title="Population by age range"></div>

The column-* class isn’t really important here; that’s just a structural setting that gives the block an appropriate amount of width that can be governed with media queries. What we really care about are the id and data-* attribute values. The id value tells the constructor what type of chart to draw and which data to use.The data attributes allow you to optionally make changes to how the chart is drawn.

Chart ID

The id tells Wazimap everything it needs to know to create this chart from the profile data. The id is broken into a few parts:

chart-<chartType>-<chartData>

The chartType, in our example case histogram, tells Wazimap the type of chart to draw. Wazimap supports:

  • pie
  • column
  • grouped_column
  • histogram
  • bar
  • grouped_bar

The chartData provides the path to the data that should fill this chart. Wazimap starts at the top, in this case demographics, and then drills down based on the rest of the keys: demographics > age > distribution. That’s where Wazimap expects to find the data to draw the chart.

Data Attributes

You can use optional data attributes to change how the chart is shown.

Use data-chart-title to specify a title to place above the chart.

Use data-initial-sort to change how pie charts are sorted. Determines which category to highlight when the chart is drawn. Using data-initial-sort="-value" will display the highest data value in the chart first. Otherwise the first value in the chart data will be used.

Use data-qualifier to add a trailing line below the chart, prepended with an “*” character. This is useful when charts require a little extra context.

Use data-stat-type to provide formatting hints for the chart’s language and display. Standard chart behavior may be overriden with these values:

  • percentage: Adds a “%” character after figures in the chart. Sets chart domain to 0-100. Uses “rate” in comparison sentences.
  • scaled-percentage: Does the same things as “percentage,” but also scales the chart so that the highest category value takes up the full vertical space available.
  • dollar: Adds a “$” character before figures in the chart. Uses “amount” in comparison sentences.