This PR makes updates to KPI outputs to give a scaled/normalised value alongside the actual KPI value.
The output types of the methods change, majority of them are now maps with two keys: "actual" and "normalised". They are still saved to csv, now with two columns: "actual" and "normalised" (examples below for the integration test files).
[Bonus] When working on this, adding tests specifically, I found a bug in GHG KPI and fixed it right there and then.
Normalisation
We normalise the KPI outputs using an affine transformation. It maps linearly the given interval (unique to each KPI) to [0,10]. I made Normaliser an interface, for if/when we want to change the type of transformation. The bounds of the mapped interval ([0,10]) were deliberately kept changeable - this made it easier to include the normaliser in tests in a simpler capacity (multiplicative constant). Thinking about it now, maybe I should have mocked this, the linear normaliser is tested separately - let me know what you think. I think being able to change scale bounds is still useful if one day we decide we want to map to [0,1] for example.
LinearNormaliser defaults to [0,10] right now, but I still wanted to specify the mapped interval in MatsimKpiGenerator. Example:
LinearNormaliser normaliser = new LinearNormaliser(0, 10, 0, 100)
maps values between 0 and 100 to values between 0 and 10.
One funny thing you will notice is that sometimes the interval for a KPI is flipped, big values are bad and small values are good. This is handled by the same normaliser (using factors of -1 under the hood) and the normaliser object is specified like this:
LinearNormaliser normaliser = new LinearNormaliser(0, 10, 100, 0)
i.e. mapping values between 100 and 0 to values between 0 and 10.
Let me know if that API is clear, I think of it as [0, 10] -> [100, 0]. I briefly considered a dedicated class for the reverse normaliser, but I thought it might look more confusing new ReverseLinearNormaliser normaliser = ReverseLinearNormaliser(0, 10, 0, 100), because you need to pay attention to the name of the class as well as the interval values. That class would be almost identical to the LinearNormaliser class too, with only a couple of small changes. I think what I've got here fells more natural but keen to get some opinions on that.
Tests
Majority of the new code is tests for the KPIs that were being normalised. The more complicated KPIs get more tests.
This also meant more content being added to the builders that set up the inputs to produce different behaviour in the tests. Very happy to be plugging those holes.
Integration tests
Outputs that are benchmarks for integration tests changed for all of the KPIs being scaled. Below are the details.
Resolves: https://github.com/arup-group/gelato/issues/69 Fixes: https://github.com/arup-group/gelato/issues/73
This PR makes updates to KPI outputs to give a scaled/normalised value alongside the actual KPI value. The output types of the methods change, majority of them are now maps with two keys:
"actual"
and"normalised"
. They are still saved to csv, now with two columns:"actual"
and"normalised"
(examples below for the integration test files).[Bonus] When working on this, adding tests specifically, I found a bug in GHG KPI and fixed it right there and then.
Normalisation
We normalise the KPI outputs using an affine transformation. It maps linearly the given interval (unique to each KPI) to
[0,10]
. I madeNormaliser
an interface, for if/when we want to change the type of transformation. The bounds of the mapped interval ([0,10]
) were deliberately kept changeable - this made it easier to include the normaliser in tests in a simpler capacity (multiplicative constant). Thinking about it now, maybe I should have mocked this, the linear normaliser is tested separately - let me know what you think. I think being able to change scale bounds is still useful if one day we decide we want to map to[0,1]
for example.LinearNormaliser
defaults to[0,10]
right now, but I still wanted to specify the mapped interval inMatsimKpiGenerator
. Example:maps values between
0
and100
to values between0
and10
.One funny thing you will notice is that sometimes the interval for a KPI is flipped, big values are bad and small values are good. This is handled by the same normaliser (using factors of
-1
under the hood) and the normaliser object is specified like this:i.e. mapping values between
100
and0
to values between0
and10
.Let me know if that API is clear, I think of it as
[0, 10] -> [100, 0]
. I briefly considered a dedicated class for the reverse normaliser, but I thought it might look more confusingnew ReverseLinearNormaliser normaliser = ReverseLinearNormaliser(0, 10, 0, 100)
, because you need to pay attention to the name of the class as well as the interval values. That class would be almost identical to theLinearNormaliser
class too, with only a couple of small changes. I think what I've got here fells more natural but keen to get some opinions on that.Tests
Majority of the new code is tests for the KPIs that were being normalised. The more complicated KPIs get more tests. This also meant more content being added to the builders that set up the inputs to produce different behaviour in the tests. Very happy to be plugging those holes.
Integration tests
Outputs that are benchmarks for integration tests changed for all of the KPIs being scaled. Below are the details.
smol
drt