Source code for durationRepresentation
# -*- coding: utf-8 -*-
"""Orders a set of representation values to fit several candidate value sets"""
import warnings
import numpy as np
import pandas as pd
[docs]def durationRepresentation(
candidates,
clusterOrder,
distributionPeriodWise,
timeStepsPerPeriod,
representMinMax=False,
):
"""
Represents the candidates of a given cluster group (clusterOrder)
such that for every attribute the number of time steps is best fit.
:param candidates: Dissimilarity matrix where each row represents a candidate
:type candidates: np.ndarray
:param clusterOrder: Integer array where the index refers to the candidate and the Integer entry to the group
:type clusterOrder: np.array
:param representMinMax: If in every cluster the minimum and the maximum of the attribute should be represented
:type representMinMax: bool
"""
# make pd.DataFrame each row represents a candidate, and the columns are defined by two levels: the attributes and
# the time steps inside the candidates.
columnTuples = []
for i in range(int(candidates.shape[1] / timeStepsPerPeriod)):
for j in range(timeStepsPerPeriod):
columnTuples.append((i, j))
candidates = pd.DataFrame(
candidates, columns=pd.MultiIndex.from_tuples(columnTuples)
)
# There are two options for the duration representation. Either, the distribution of each cluster is preserved
# (periodWise = True) or the distribution of the total time series is preserved only. In the latter case, the
# inner-cluster variance is smaller and the variance across the typical periods' mean values is higher
if distributionPeriodWise:
clusterCenters = []
for clusterNum in np.unique(clusterOrder):
indice = np.where(clusterOrder == clusterNum)
noCandidates = len(indice[0])
clean_index = []
clusterCenter = []
# get a clean index depending on the size
for y in candidates.columns.levels[1]:
for x in range(noCandidates):
clean_index.append((x, y))
for a in candidates.columns.levels[0]:
# get all the values of a certain attribute and cluster
candidateValues = candidates.loc[indice[0], a]
# sort all values
sortedAttr = candidateValues.stack().sort_values()
# reindex and arrange such that every sorted segment gets represented by its mean
sortedAttr.index = pd.MultiIndex.from_tuples(clean_index)
representationValues = sortedAttr.unstack(level=0).mean(axis=1)
# respect max and min of the attributes
if representMinMax:
representationValues.loc[0] = sortedAttr.values[0]
representationValues.loc[
representationValues.index[-1]
] = sortedAttr.values[-1]
# get the order of the representation values such that euclidean distance to the candidates is minimized
order = candidateValues.mean().sort_values().index
# arrange
representationValues.index = order
representationValues.sort_index(inplace=True)
# add to cluster center
clusterCenter = np.append(clusterCenter, representationValues.values)
clusterCenters.append(clusterCenter)
else:
clusterCentersList = []
for a in candidates.columns.levels[0]:
meanVals = []
clusterLengths = []
for clusterNum in np.unique(clusterOrder):
indice = np.where(clusterOrder == clusterNum)
noCandidates = len(indice[0])
# get all the values of a certain attribute and cluster
candidateValues = candidates.loc[indice[0], a]
# calculate centroid of each cluster and append to list
meanVals.append(candidateValues.mean())
# make a list of weights of each cluster for each time step within the period
clusterLengths.append(np.repeat(noCandidates, timeStepsPerPeriod))
# concat centroid values and cluster weights for all clusters
meansAndWeights = pd.concat(
[
pd.DataFrame(np.array(meanVals)).stack(),
pd.DataFrame(np.array(clusterLengths)).stack(),
],
axis=1,
)
# sort all values of all clusters according to the centroid values
meansAndWeightsSorted = meansAndWeights.sort_values(0)
# save order of the sorted centroid values across all clusters
order = meansAndWeightsSorted.index
# sort all values of the original time series
sortedAttr = candidates.loc[:, a].stack().sort_values().values
# take mean of sections of the original duration curve according to the cluster and its weight the
# respective section is assigned to
representationValues = []
counter = 0
for i, j in enumerate(meansAndWeightsSorted[1]):
representationValues.append(sortedAttr[counter : counter + j].mean())
counter += j
# respect max and min of the attributes
if representMinMax:
representationValues = _representMinMax(
representationValues,
sortedAttr,
meansAndWeightsSorted,
keepSum=True,
)
# transform all representation values to a data frame and arrange it
# according to the order of the sorted
# centroid values
representationValues = pd.DataFrame(np.array(representationValues))
representationValues.index = order
representationValues.sort_index(inplace=True)
# append all cluster values attribute-wise to a list
clusterCentersList.append(representationValues.unstack())
# rearrange so that rows are the cluster centers and columns are time steps x attributes
clusterCenters = np.array(pd.concat(clusterCentersList, axis=1))
return clusterCenters
def _representMinMax(representationValues, sortedAttr, meansAndWeightsSorted,
keepSum=True):
"""
Represents the the min and max values of the original time series in the
duration curve representation such that the min and max values of the
original time series are preserved.
:param representationValues: The duration curve representation values
:type representationValues: np.array
:param sortedAttr: The sorted original time series
:type sortedAttr: np.array
:param meansAndWeightsSorted: The number of occureance of
the original time series.
:type meansAndWeightsSorted: pd.DataFrame
:param keepSum: If the sum of the duration curve should be preserved
:type keepSum: bool
"""
if np.any(np.array(representationValues) < 0):
raise ValueError("Negative values in the duration curve representation")
# first retrieve the change of the values to the min and max values
# of the original time series and their duration in the original
# time series
delta_max = sortedAttr.max() - representationValues[-1]
appearance_max = meansAndWeightsSorted[1].iloc[-1]
delta_min = sortedAttr.min() - representationValues[0]
appearance_min = meansAndWeightsSorted[1].iloc[0]
if delta_min == 0 and delta_max == 0:
return representationValues
if keepSum:
# now anticipate the shift of the sum of the time series
# due to the change of the min and max values
# of the duration curve
delta_sum = delta_max * appearance_max + delta_min * appearance_min
# and derive how much the other values have to be changed to preserve
# the mean of the duration curve
correction_factor = - delta_sum / (meansAndWeightsSorted[1].iloc[1:-1]
* representationValues[1:-1]).sum()
if correction_factor < -1 or correction_factor > 1:
warnings.warn("The cluster is to small to preserve the sum of the duration curve and additionally the min and max values of the original cluster members. The min max values of the cluster are not preserved. This does not necessarily mean that the min and max values of the original time series are not preserved.")
return representationValues
# correct the values of the duration curve such
# that the mean of the duration curve is preserved
# since the min and max values are changed
representationValues[1:-1] = np.multiply(representationValues[1:-1], (
1+ correction_factor))
# change the values of the duration curve such that the min and max
# values are preserved
representationValues[-1] += delta_max
representationValues[0] += delta_min
return representationValues