2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
// userStatistics provides functions to generate statistics about stored user profiles.
|
|
|
|
// The user chooses the attribute(s), and the gui displays a chart with a button to view the statistics data.
|
|
|
|
// See statisticsGui.go to see the gui code.
|
|
|
|
|
|
|
|
package userStatistics
|
|
|
|
|
|
|
|
//TODO: Add the ability to control for confounding variables
|
|
|
|
// Example: Wealth, controlled for age and sex
|
|
|
|
|
|
|
|
import "seekia/internal/badgerDatabase"
|
|
|
|
import "seekia/internal/helpers"
|
|
|
|
import "seekia/internal/profiles/calculatedAttributes"
|
|
|
|
import "seekia/internal/profiles/profileStorage"
|
|
|
|
import "seekia/internal/profiles/attributeDisplay"
|
|
|
|
import "seekia/internal/translation"
|
2024-06-07 02:04:13 +02:00
|
|
|
import "seekia/internal/statisticsDatum"
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
import "slices"
|
|
|
|
import "strings"
|
|
|
|
import "errors"
|
|
|
|
import "math"
|
|
|
|
|
|
|
|
|
|
|
|
//Outputs:
|
|
|
|
// -int: Number of users analyzed in statistics
|
2024-06-07 02:04:13 +02:00
|
|
|
// -[]statisticsDatum.StatisticsDatum: Statistics datums list (sorted, not grouped)
|
2024-04-11 15:51:56 +02:00
|
|
|
// -bool: Grouping performed
|
2024-06-07 02:04:13 +02:00
|
|
|
// -[]statisticsDatum.StatisticsDatum: Grouped datums list
|
2024-04-11 15:51:56 +02:00
|
|
|
// -func(float64)(string, error): Function to format y axis values
|
|
|
|
// -This is used because the values must be passed to the chart code as pure floats, but they must be formatted after to be human readable
|
|
|
|
// -Example: "1000000" -> "1 million"
|
|
|
|
// -error
|
2024-06-07 02:04:13 +02:00
|
|
|
func GetUserStatisticsDatumsLists_BarChart(identityType string,
|
2024-04-11 15:51:56 +02:00
|
|
|
networkType byte,
|
|
|
|
xAxisAttribute string,
|
|
|
|
xAxisIsNumerical bool,
|
|
|
|
formatXAxisValuesFunction func(string)(string, error),
|
|
|
|
xAxisUnknownLabel string,
|
2024-06-07 02:04:13 +02:00
|
|
|
yAxisAttribute string)(int, []statisticsDatum.StatisticsDatum, bool, []statisticsDatum.StatisticsDatum, func(float64)(string, error), error){
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
isValid := helpers.VerifyNetworkType(networkType)
|
|
|
|
if (isValid == false){
|
|
|
|
networkTypeString := helpers.ConvertByteToString(networkType)
|
2024-06-07 02:04:13 +02:00
|
|
|
return 0, nil, false, nil, nil, errors.New("GetUserStatisticsDatumsLists_BarChart called with invalid networkType: " + networkTypeString)
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
getYAxisRoundingPrecision := func()int{
|
|
|
|
if (yAxisAttribute == "Number Of Users"){
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
if (yAxisAttribute == "Average Height"){
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
if (yAxisAttribute == "Average Age"){
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
yAxisRoundingPrecision := getYAxisRoundingPrecision()
|
|
|
|
|
|
|
|
//Outputs:
|
|
|
|
// -int: Total analyzed users
|
2024-06-07 02:04:13 +02:00
|
|
|
// -[]statisticsDatum.StatisticsDatum: Statistics datums list
|
2024-04-11 15:51:56 +02:00
|
|
|
// -map[string]int: Response Counts map (X axis attribute response -> Number of y axis responses)
|
|
|
|
// -bool: yAxisIsAverage
|
|
|
|
// -map[string]float64: Response Sums map (X axis attribute response -> all y axis responses summed) (If yAxisIsAverage == true)
|
|
|
|
// -func(float64)(string, error): Function to format statistics values
|
2024-06-07 02:04:13 +02:00
|
|
|
// -bool: Include a No Response/Unknown value datum
|
2024-04-11 15:51:56 +02:00
|
|
|
// -This is only needed if at least 1 user did not respond to the X axis attribute on their profile.
|
2024-06-07 02:04:13 +02:00
|
|
|
// -statisticsDatum.StatisticsDatum: The unknown value datum.
|
2024-04-11 15:51:56 +02:00
|
|
|
// -error
|
2024-06-07 02:04:13 +02:00
|
|
|
getStatisticsDatumsList := func()(int, []statisticsDatum.StatisticsDatum, map[string]int, bool, map[string]float64, func(float64)(string, error), bool, statisticsDatum.StatisticsDatum, error){
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
//TODO: Add "Probability Of ..."
|
|
|
|
// This will allow viewing of choice attribute probabilities
|
|
|
|
// For example: Probability of being Male, probability of being Female, etc.
|
|
|
|
// We need to add this for each canonical option
|
|
|
|
|
|
|
|
if (yAxisAttribute == "Number Of Users"){
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
totalAnalyzedUsers, statisticsDatumsList, responseCountsMap, numberOfUnknownValueUsers, err := getProfileAttributeCountStatisticsDatumsList(identityType, networkType, xAxisAttribute, formatXAxisValuesFunction)
|
|
|
|
if (err != nil) { return 0, nil, nil, false, nil, nil, false, statisticsDatum.StatisticsDatum{}, err }
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
formatValuesFunction := func(input float64)(string, error){
|
|
|
|
|
|
|
|
// input will be an whole number representing the number of users
|
|
|
|
|
|
|
|
result := helpers.ConvertFloat64ToStringRounded(input, 0)
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if (numberOfUnknownValueUsers == 0){
|
2024-06-07 02:04:13 +02:00
|
|
|
return totalAnalyzedUsers, statisticsDatumsList, responseCountsMap, false, nil, formatValuesFunction, false, statisticsDatum.StatisticsDatum{}, nil
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
unknownValueFormatted := helpers.ConvertIntToString(numberOfUnknownValueUsers)
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
unknownDatum := statisticsDatum.StatisticsDatum{
|
2024-04-11 15:51:56 +02:00
|
|
|
Label: xAxisUnknownLabel,
|
|
|
|
LabelFormatted: xAxisUnknownLabel,
|
|
|
|
Value: float64(numberOfUnknownValueUsers),
|
|
|
|
ValueFormatted: unknownValueFormatted,
|
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
return totalAnalyzedUsers, statisticsDatumsList, responseCountsMap, false, nil, formatValuesFunction, true, unknownDatum, nil
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
userIdentityHashesToAnalyzeList, err := getUserIdentityHashesToAnalyzeList(identityType)
|
2024-06-07 02:04:13 +02:00
|
|
|
if (err != nil) { return 0, nil, nil, false, nil, nil, false, statisticsDatum.StatisticsDatum{}, err }
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
yAxisIsAverage := strings.HasPrefix(yAxisAttribute, "Average ")
|
|
|
|
if (yAxisIsAverage == true){
|
|
|
|
|
|
|
|
// This function will return the attribute name
|
|
|
|
// We must convert the attribute title to the attribute name
|
|
|
|
getAttributeToGetAverageFor := func()string{
|
|
|
|
|
|
|
|
attributeTitle := strings.TrimPrefix(yAxisAttribute, "Average ")
|
2024-06-07 02:04:13 +02:00
|
|
|
|
|
|
|
switch attributeTitle{
|
|
|
|
|
|
|
|
case "Wealth":{
|
|
|
|
return "WealthInGold"
|
|
|
|
}
|
|
|
|
case "23andMe Neanderthal Variants":{
|
|
|
|
return "23andMe_NeanderthalVariants"
|
|
|
|
}
|
|
|
|
case "Body Fat":{
|
|
|
|
return "BodyFat"
|
|
|
|
}
|
|
|
|
case "Body Muscle":{
|
|
|
|
return "BodyMuscle"
|
|
|
|
}
|
|
|
|
case "Fruit Rating":{
|
|
|
|
return "FruitRating"
|
|
|
|
}
|
|
|
|
case "Vegetables Rating":{
|
|
|
|
return "VegetablesRating"
|
|
|
|
}
|
|
|
|
case "Nuts Rating":{
|
|
|
|
return "NutsRating"
|
|
|
|
}
|
|
|
|
case "Grains Rating":{
|
|
|
|
return "GrainsRating"
|
|
|
|
}
|
|
|
|
case "Dairy Rating":{
|
|
|
|
return "DairyRating"
|
|
|
|
}
|
|
|
|
case "Seafood Rating":{
|
|
|
|
return "SeafoodRating"
|
|
|
|
}
|
|
|
|
case "Beef Rating":{
|
|
|
|
return "BeefRating"
|
|
|
|
}
|
|
|
|
case "Pork Rating":{
|
|
|
|
return "PorkRating"
|
|
|
|
}
|
|
|
|
case "Poultry Rating":{
|
|
|
|
return "PoultryRating"
|
|
|
|
}
|
|
|
|
case "Eggs Rating":{
|
|
|
|
return "EggsRating"
|
|
|
|
}
|
|
|
|
case "Beans Rating":{
|
|
|
|
return "BeansRating"
|
|
|
|
}
|
|
|
|
case "Alcohol Frequency":{
|
|
|
|
return "AlcoholFrequency"
|
|
|
|
}
|
|
|
|
case "Tobacco Frequency":{
|
|
|
|
return "TobaccoFrequency"
|
|
|
|
}
|
|
|
|
case "Cannabis Frequency":{
|
|
|
|
return "CannabisFrequency"
|
|
|
|
}
|
|
|
|
case "Pets Rating":{
|
|
|
|
return "PetsRating"
|
|
|
|
}
|
|
|
|
case "Dogs Rating":{
|
|
|
|
return "DogsRating"
|
|
|
|
}
|
|
|
|
case "Cats Rating":{
|
|
|
|
return "CatsRating"
|
|
|
|
}
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return attributeTitle
|
|
|
|
}
|
|
|
|
|
|
|
|
attributeToGetAverageFor := getAttributeToGetAverageFor()
|
|
|
|
|
|
|
|
totalAnalyzedUsers := 0
|
|
|
|
|
|
|
|
//Map structure: X-Axis attribute -> Number of users with this x-axis attribute who have a y-axis attribute response
|
|
|
|
responseCountsMap := make(map[string]int)
|
|
|
|
//Map structure: X-axis attribute response -> All Y-axis attribute responses summed for users with this x-axis attribute
|
|
|
|
responseSumsMap := make(map[string]float64)
|
|
|
|
|
|
|
|
// This stores a count of the number of users whose X axis value is unknown
|
|
|
|
// For example, if X axis is Age, this is the number of users who did not respond
|
|
|
|
numberOfUsersWithUnknownXAxisValue := 0
|
|
|
|
// This stores the total sum of all y axis values for users who have an unknown X axis value
|
|
|
|
// For example, if the X axis attribute is age, and the Y axis attribute is average height,
|
|
|
|
// this stores the sum of heights of all users who have no age on their profile.
|
|
|
|
usersWithUnknownXAxisValueYAxisValuesSum := float64(0)
|
|
|
|
|
|
|
|
for _, userIdentityHash := range userIdentityHashesToAnalyzeList{
|
|
|
|
|
|
|
|
profileFound, getAnyUserAttributeValueFunction, err := getRetrieveAnyAttributeFromUserNewestProfileFunction(userIdentityHash, networkType)
|
2024-06-07 02:04:13 +02:00
|
|
|
if (err != nil) { return 0, nil, nil, false, nil, nil, false, statisticsDatum.StatisticsDatum{}, err }
|
2024-04-11 15:51:56 +02:00
|
|
|
if (profileFound == false){
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
userIsDisabled, _, _, err := getAnyUserAttributeValueFunction("Disabled")
|
2024-06-07 02:04:13 +02:00
|
|
|
if (err != nil) { return 0, nil, nil, false, nil, nil, false, statisticsDatum.StatisticsDatum{}, err }
|
2024-04-11 15:51:56 +02:00
|
|
|
if (userIsDisabled == true){
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
attributeExists, _, userAttributeToGetAverageForValue, err := getAnyUserAttributeValueFunction(attributeToGetAverageFor)
|
2024-06-07 02:04:13 +02:00
|
|
|
if (err != nil) { return 0, nil, nil, false, nil, nil, false, statisticsDatum.StatisticsDatum{}, err }
|
2024-04-11 15:51:56 +02:00
|
|
|
if (attributeExists == false){
|
|
|
|
// This user did not respond to the attribute we are getting the average for
|
|
|
|
// We will not add them to the statistics maps
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
totalAnalyzedUsers += 1
|
|
|
|
|
|
|
|
userAttributeToGetAverageForValueFloat64, err := helpers.ConvertStringToFloat64(userAttributeToGetAverageForValue)
|
|
|
|
if (err != nil) {
|
2024-06-07 02:04:13 +02:00
|
|
|
return 0, nil, nil, false, nil, nil, false, statisticsDatum.StatisticsDatum{}, errors.New("Database corrupt: Contains invalid " + userAttributeToGetAverageForValue + " value: " + userAttributeToGetAverageForValue)
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
attributeFound, _, userXAxisAttributeValue, err := getAnyUserAttributeValueFunction(xAxisAttribute)
|
2024-06-07 02:04:13 +02:00
|
|
|
if (err != nil) { return 0, nil, nil, false, nil, nil, false, statisticsDatum.StatisticsDatum{}, err }
|
2024-04-11 15:51:56 +02:00
|
|
|
if (attributeFound == false){
|
|
|
|
// This user did not respond to the X axis attribute
|
|
|
|
// We calculate the average for users who do not respond and put it in its own category
|
|
|
|
|
|
|
|
numberOfUsersWithUnknownXAxisValue += 1
|
|
|
|
usersWithUnknownXAxisValueYAxisValuesSum += userAttributeToGetAverageForValueFloat64
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
responseCountsMap[userXAxisAttributeValue] += 1
|
|
|
|
|
|
|
|
responseSumsMap[userXAxisAttributeValue] += userAttributeToGetAverageForValueFloat64
|
|
|
|
}
|
|
|
|
|
|
|
|
_, _, formatYAxisValuesFunction, _, _, err := attributeDisplay.GetProfileAttributeDisplayInfo(attributeToGetAverageFor)
|
2024-06-07 02:04:13 +02:00
|
|
|
if (err != nil) { return 0, nil, nil, false, nil, nil, false, statisticsDatum.StatisticsDatum{}, err }
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
statisticsDatumsList := make([]statisticsDatum.StatisticsDatum, 0, len(responseCountsMap))
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
for attributeResponse, responsesCount := range responseCountsMap{
|
|
|
|
|
|
|
|
attributeResponseFormatted, err := formatXAxisValuesFunction(attributeResponse)
|
2024-06-07 02:04:13 +02:00
|
|
|
if (err != nil) { return 0, nil, nil, false, nil, nil, false, statisticsDatum.StatisticsDatum{}, err }
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
allResponsesSum, exists := responseSumsMap[attributeResponse]
|
|
|
|
if (exists == false){
|
2024-06-07 02:04:13 +02:00
|
|
|
return 0, nil, nil, false, nil, nil, false, statisticsDatum.StatisticsDatum{}, errors.New("Response sums map missing attribute value")
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
averageValue := allResponsesSum/float64(responsesCount)
|
|
|
|
|
|
|
|
averageValueString := helpers.ConvertFloat64ToStringRounded(averageValue, yAxisRoundingPrecision)
|
|
|
|
|
|
|
|
averageValueFormatted, err := formatYAxisValuesFunction(averageValueString)
|
2024-06-07 02:04:13 +02:00
|
|
|
if (err != nil) { return 0, nil, nil, false, nil, nil, false, statisticsDatum.StatisticsDatum{}, err }
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
newStatisticsDatum := statisticsDatum.StatisticsDatum{
|
2024-04-11 15:51:56 +02:00
|
|
|
Label: attributeResponse,
|
|
|
|
LabelFormatted: attributeResponseFormatted,
|
|
|
|
Value: averageValue,
|
|
|
|
ValueFormatted: averageValueFormatted,
|
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
statisticsDatumsList = append(statisticsDatumsList, newStatisticsDatum)
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// We use this function to format values after grouping, if grouping is needed
|
|
|
|
formatValuesFunction := func(input float64)(string, error){
|
|
|
|
|
|
|
|
inputString := helpers.ConvertFloat64ToStringRounded(input, yAxisRoundingPrecision)
|
|
|
|
|
|
|
|
valueFormatted, err := formatYAxisValuesFunction(inputString)
|
|
|
|
if (err != nil) { return "", err }
|
|
|
|
|
|
|
|
return valueFormatted, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if (numberOfUsersWithUnknownXAxisValue == 0){
|
2024-06-07 02:04:13 +02:00
|
|
|
return totalAnalyzedUsers, statisticsDatumsList, responseCountsMap, true, responseSumsMap, formatValuesFunction, false, statisticsDatum.StatisticsDatum{}, nil
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
unknownResponsesAverage := usersWithUnknownXAxisValueYAxisValuesSum/float64(numberOfUsersWithUnknownXAxisValue)
|
|
|
|
|
|
|
|
unknownResponsesValueFormatted, err := formatValuesFunction(unknownResponsesAverage)
|
2024-06-07 02:04:13 +02:00
|
|
|
if (err != nil) { return 0, nil, nil, false, nil, nil, false, statisticsDatum.StatisticsDatum{}, err }
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
// This datum represents the average value for the yAxisAttribute for users who did not respond.
|
2024-04-11 15:51:56 +02:00
|
|
|
// For example, if the xAxisAttribute is Height, and the yAxisAttribute is AverageWealth, this value
|
|
|
|
// will represent the average wealth for users who did not provide Height on their profile.
|
2024-06-07 02:04:13 +02:00
|
|
|
unknownStatisticsDatum := statisticsDatum.StatisticsDatum{
|
2024-04-11 15:51:56 +02:00
|
|
|
Label: xAxisUnknownLabel,
|
|
|
|
LabelFormatted: xAxisUnknownLabel,
|
|
|
|
Value: unknownResponsesAverage,
|
|
|
|
ValueFormatted: unknownResponsesValueFormatted,
|
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
return totalAnalyzedUsers, statisticsDatumsList, responseCountsMap, true, responseSumsMap, formatValuesFunction, true, unknownStatisticsDatum, nil
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
return 0, nil, nil, false, nil, nil, false, statisticsDatum.StatisticsDatum{}, errors.New("Invalid y-axis attribute: " + yAxisAttribute)
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
totalAnalyzedUsers, statisticsDatumsList, responseCountsMap, yAxisIsAverage, responseSumsMap, formatValuesFunction, includeUnknownDatum, unknownValueDatum, err := getStatisticsDatumsList()
|
2024-04-11 15:51:56 +02:00
|
|
|
if (err != nil) { return 0, nil, false, nil, nil, err }
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
sortStatisticsDatumsList(statisticsDatumsList, xAxisIsNumerical)
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
// We now see if we need to group the datums in the list together
|
2024-04-11 15:51:56 +02:00
|
|
|
// We do this if there are more than 10 categories
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
if (len(statisticsDatumsList) <= 10){
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
// No grouping needed. We are done.
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
if (includeUnknownDatum == true){
|
|
|
|
statisticsDatumsList = append(statisticsDatumsList, unknownValueDatum)
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
return totalAnalyzedUsers, statisticsDatumsList, false, nil, formatValuesFunction, nil
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
groupedStatisticsDatumsList, err := getStatisticsDatumsListGrouped(10, statisticsDatumsList, xAxisIsNumerical, responseCountsMap, yAxisIsAverage, responseSumsMap, formatValuesFunction)
|
2024-04-11 15:51:56 +02:00
|
|
|
if (err != nil) { return 0, nil, false, nil, nil, err }
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
if (includeUnknownDatum == true){
|
|
|
|
statisticsDatumsList = append(statisticsDatumsList, unknownValueDatum)
|
|
|
|
groupedStatisticsDatumsList = append(groupedStatisticsDatumsList, unknownValueDatum)
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
return totalAnalyzedUsers, statisticsDatumsList, true, groupedStatisticsDatumsList, formatValuesFunction, nil
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//Outputs:
|
|
|
|
// -int: Number of users analyzed in statistics
|
2024-06-07 02:04:13 +02:00
|
|
|
// -[]statisticsDatum.StatisticsDatum: Statistics datums list (sorted, not grouped)
|
2024-04-11 15:51:56 +02:00
|
|
|
// -bool: Grouping performed
|
2024-06-07 02:04:13 +02:00
|
|
|
// -[]statisticsDatum.StatisticsDatum: Grouped datums list
|
2024-04-11 15:51:56 +02:00
|
|
|
// -error
|
2024-06-07 02:04:13 +02:00
|
|
|
func GetUserStatisticsDatumsLists_DonutChart(identityType string,
|
2024-04-11 15:51:56 +02:00
|
|
|
networkType byte,
|
|
|
|
attributeToAnalyze string,
|
|
|
|
attributeIsNumerical bool,
|
|
|
|
formatAttributeLabelsFunction func(string)(string, error),
|
2024-06-07 02:04:13 +02:00
|
|
|
unknownLabelTranslated string)(int, []statisticsDatum.StatisticsDatum, bool, []statisticsDatum.StatisticsDatum, error){
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
isValid := helpers.VerifyNetworkType(networkType)
|
|
|
|
if (isValid == false){
|
|
|
|
networkTypeString := helpers.ConvertByteToString(networkType)
|
2024-06-07 02:04:13 +02:00
|
|
|
return 0, nil, false, nil, errors.New("GetUserStatisticsDatumsLists_DonutChart called with invalid networkType: " + networkTypeString)
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
totalAnalyzedUsers, statisticsDatumsList, attributeCountsMap, numberOfUnknownResponders, err := getProfileAttributeCountStatisticsDatumsList(identityType, networkType, attributeToAnalyze, formatAttributeLabelsFunction)
|
2024-04-11 15:51:56 +02:00
|
|
|
if (err != nil) { return 0, nil, false, nil, err }
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
sortStatisticsDatumsList(statisticsDatumsList, attributeIsNumerical)
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
getUnknownValueDatum := func()statisticsDatum.StatisticsDatum{
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
numberOfUnknownRespondersString := helpers.ConvertIntToString(numberOfUnknownResponders)
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
unknownValueDatum := statisticsDatum.StatisticsDatum{
|
2024-04-11 15:51:56 +02:00
|
|
|
Label: unknownLabelTranslated,
|
|
|
|
LabelFormatted: unknownLabelTranslated,
|
|
|
|
Value: float64(numberOfUnknownResponders),
|
|
|
|
ValueFormatted: numberOfUnknownRespondersString,
|
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
return unknownValueDatum
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
if (len(statisticsDatumsList) <= 8){
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
// No grouping needed.
|
|
|
|
|
|
|
|
if (numberOfUnknownResponders != 0){
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
unknownValueDatum := getUnknownValueDatum()
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
statisticsDatumsList = append(statisticsDatumsList, unknownValueDatum)
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
return totalAnalyzedUsers, statisticsDatumsList, false, nil, nil
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
formatValuesFunction := func(input float64)(string, error){
|
|
|
|
|
|
|
|
// input will always be the number of users who responded with the value
|
|
|
|
// Thus, input will always be an integer
|
|
|
|
|
|
|
|
result := helpers.ConvertFloat64ToStringRounded(input, 0)
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
groupedStatisticsDatumsList, err := getStatisticsDatumsListGrouped(8, statisticsDatumsList, attributeIsNumerical, attributeCountsMap, false, nil, formatValuesFunction)
|
2024-04-11 15:51:56 +02:00
|
|
|
if (err != nil) { return 0, nil, false, nil, err }
|
|
|
|
|
|
|
|
if (numberOfUnknownResponders != 0){
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
unknownValueDatum := getUnknownValueDatum()
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
statisticsDatumsList = append(statisticsDatumsList, unknownValueDatum)
|
|
|
|
groupedStatisticsDatumsList = append(groupedStatisticsDatumsList, unknownValueDatum)
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
return totalAnalyzedUsers, statisticsDatumsList, true, groupedStatisticsDatumsList, nil
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
// This function will return a statistics datums list of the following format:
|
2024-04-11 15:51:56 +02:00
|
|
|
// "Label": Attribute name (Example: "Male")
|
|
|
|
// "Value": The number of users who responded with the attribute (in this example: "Male")
|
|
|
|
//
|
|
|
|
// All users of provided identityType who are not disabled will be analyzed
|
|
|
|
// -int: Number of analyzed users
|
2024-06-07 02:04:13 +02:00
|
|
|
// -[]statisticsDatum.StatisticsDatum: Statistics datums list (not sorted or grouped)
|
2024-04-11 15:51:56 +02:00
|
|
|
// -map[string]int: Response counts map (Response -> Number of responders)
|
|
|
|
// -int: Number of No Response/Unknown value responders
|
|
|
|
// -error
|
2024-06-07 02:04:13 +02:00
|
|
|
func getProfileAttributeCountStatisticsDatumsList(identityType string,
|
2024-04-11 15:51:56 +02:00
|
|
|
networkType byte,
|
|
|
|
attributeName string,
|
2024-06-07 02:04:13 +02:00
|
|
|
formatLabelsFunction func(string)(string, error))(int, []statisticsDatum.StatisticsDatum, map[string]int, int, error){
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
userIdentityHashesToAnalyzeList, err := getUserIdentityHashesToAnalyzeList(identityType)
|
|
|
|
if (err != nil) { return 0, nil, nil, 0, err }
|
|
|
|
|
|
|
|
totalAnalyzedUsers := 0
|
|
|
|
|
|
|
|
// Map structure: User Attribute response -> Number of users who responded with the response
|
|
|
|
responseCountsMap := make(map[string]int)
|
|
|
|
|
|
|
|
// This stores the number of users for whom we do not know their value
|
|
|
|
numberOfUnknownValueUsers := 0
|
|
|
|
|
|
|
|
for _, userIdentityHash := range userIdentityHashesToAnalyzeList{
|
|
|
|
|
|
|
|
profileFound, getAnyUserAttributeValueFunction, err := getRetrieveAnyAttributeFromUserNewestProfileFunction(userIdentityHash, networkType)
|
|
|
|
if (err != nil) { return 0, nil, nil, 0, err }
|
|
|
|
if (profileFound == false){
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
userIsDisabled, _, _, err := getAnyUserAttributeValueFunction("Disabled")
|
|
|
|
if (err != nil) { return 0, nil, nil, 0, err }
|
|
|
|
if (userIsDisabled == true){
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
totalAnalyzedUsers += 1
|
|
|
|
|
|
|
|
attributeFound, _, attributeValue, err := getAnyUserAttributeValueFunction(attributeName)
|
|
|
|
if (err != nil) { return 0, nil, nil, 0, err }
|
|
|
|
if (attributeFound == false){
|
|
|
|
numberOfUnknownValueUsers += 1
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
responseCountsMap[attributeValue] += 1
|
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
statisticsDatumsList := make([]statisticsDatum.StatisticsDatum, 0, len(responseCountsMap))
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
for attributeResponse, numberOfUsers := range responseCountsMap{
|
|
|
|
|
|
|
|
attributeResponseFormatted, err := formatLabelsFunction(attributeResponse)
|
|
|
|
if (err != nil) { return 0, nil, nil, 0, err }
|
|
|
|
|
|
|
|
attributeNumberOfUsersString := helpers.ConvertIntToString(numberOfUsers)
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
newStatisticsDatum := statisticsDatum.StatisticsDatum{
|
2024-04-11 15:51:56 +02:00
|
|
|
Label: attributeResponse,
|
|
|
|
LabelFormatted: attributeResponseFormatted,
|
|
|
|
Value: float64(numberOfUsers),
|
|
|
|
ValueFormatted: attributeNumberOfUsersString,
|
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
statisticsDatumsList = append(statisticsDatumsList, newStatisticsDatum)
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
return totalAnalyzedUsers, statisticsDatumsList, responseCountsMap, numberOfUnknownValueUsers, nil
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
func sortStatisticsDatumsList(inputStatisticsDatumsList []statisticsDatum.StatisticsDatum, labelIsNumerical bool){
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
if (len(inputStatisticsDatumsList) <= 1){
|
2024-04-11 15:51:56 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if (labelIsNumerical == true){
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
// We sort the datums by label values in ascending order
|
2024-04-11 15:51:56 +02:00
|
|
|
// Example: Bar chart columns are ages, in order of youngest to oldest
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
compareDatumsFunction := func(datumA statisticsDatum.StatisticsDatum, datumB statisticsDatum.StatisticsDatum)int{
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
datumALabel := datumA.Label
|
|
|
|
datumBLabel := datumB.Label
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
datumAFloat64, err := helpers.ConvertStringToFloat64(datumALabel)
|
2024-04-11 15:51:56 +02:00
|
|
|
if (err != nil) {
|
2024-06-07 02:04:13 +02:00
|
|
|
panic("Invalid statistics datum: Datum Label is not float: " + datumALabel)
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
datumBFloat64, err := helpers.ConvertStringToFloat64(datumBLabel)
|
2024-04-11 15:51:56 +02:00
|
|
|
if (err != nil) {
|
2024-06-07 02:04:13 +02:00
|
|
|
panic("Invalid statistics datum: Datum Label is not float: " + datumBLabel)
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
if (datumAFloat64 == datumBFloat64){
|
2024-04-11 15:51:56 +02:00
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
if (datumAFloat64 < datumBFloat64){
|
2024-04-11 15:51:56 +02:00
|
|
|
return -1
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
slices.SortFunc(inputStatisticsDatumsList, compareDatumsFunction)
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
// We sort the datums by their values in descending order
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
compareDatumsFunction := func(datum1 statisticsDatum.StatisticsDatum, datum2 statisticsDatum.StatisticsDatum)int{
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
datum1Value := datum1.Value
|
|
|
|
datum2Value := datum2.Value
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
if (datum1Value == datum2Value){
|
2024-04-11 15:51:56 +02:00
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
if (datum1Value < datum2Value){
|
2024-04-11 15:51:56 +02:00
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
slices.SortFunc(inputStatisticsDatumsList, compareDatumsFunction)
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
// This function will group a statistics datums list.
|
2024-04-11 15:51:56 +02:00
|
|
|
// It will group Labels and their values to fit into a specified number of groups
|
|
|
|
// Example: "1","2","3","4" -> "1-2", "3-4"
|
|
|
|
//Inputs:
|
|
|
|
// -int: Maximum groups to create
|
2024-06-07 02:04:13 +02:00
|
|
|
// -[]statisticsDatum.StatisticsDatum: Statistics datums list to group
|
2024-04-11 15:51:56 +02:00
|
|
|
// -bool: Label is numerical
|
|
|
|
// -If it is, we will group labels into groups of numbers.
|
|
|
|
// -Otherwise, we will group all categories after the first maximumGroupsToCreate into a group called Other
|
|
|
|
// -map[string]int
|
|
|
|
// -Response Counts map (Attribute response -> Number of responders with that response)
|
|
|
|
// -bool: Value is average
|
|
|
|
// -If value is average, we will combine the values of each group and find their average
|
|
|
|
// -Otherwise, we will find their sum
|
|
|
|
// -map[string]float64
|
|
|
|
// -Response Sums map (X-axis attribute response -> all Y-axis responses summed) (If yAxisIsAverage == true)
|
|
|
|
// -func(float64)(string, error): This is the function we use to format the values
|
|
|
|
//Outputs:
|
2024-06-07 02:04:13 +02:00
|
|
|
// -[]statisticsDatum.StatisticsDatum: Grouped statistics datums list
|
2024-04-11 15:51:56 +02:00
|
|
|
// -error
|
2024-06-07 02:04:13 +02:00
|
|
|
func getStatisticsDatumsListGrouped(maximumGroupsToCreate int,
|
|
|
|
inputStatisticsDatumsList []statisticsDatum.StatisticsDatum,
|
2024-04-11 15:51:56 +02:00
|
|
|
labelIsNumerical bool,
|
|
|
|
responseCountsMap map[string]int,
|
|
|
|
valueIsAverage bool,
|
|
|
|
responseSumsMap map[string]float64,
|
2024-06-07 02:04:13 +02:00
|
|
|
formatValuesFunction func(float64)(string, error))([]statisticsDatum.StatisticsDatum, error){
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
if (len(inputStatisticsDatumsList) <= maximumGroupsToCreate){
|
|
|
|
return nil, errors.New("maximumGroupsToCreate is <= length of input statistics datums list")
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
// We deep copy the statistics datums list to retain the sorted version
|
2024-04-11 15:51:56 +02:00
|
|
|
// We need to retain both versions because the user can view the raw or grouped data in the GUI
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
statisticsDatumsList := slices.Clone(inputStatisticsDatumsList)
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
// We use this function to get the new value for a group of labels
|
2024-06-07 02:04:13 +02:00
|
|
|
getGroupValue := func(datumsToCombineList []statisticsDatum.StatisticsDatum)(float64, error){
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
// This will count the total number of users who responded with the responses within this group
|
|
|
|
// Example: Labels are "Blue", "Green", this variable will store the number of users who responded with either Blue or Green
|
|
|
|
totalRespondersCount := float64(0)
|
|
|
|
|
|
|
|
// This will store the sum for all response values within the group
|
|
|
|
// We only need to add to this sum if valueIsAverage == true
|
|
|
|
allReponsesSummed := float64(0)
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
for _, statisticsDatum := range datumsToCombineList{
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
datumLabel := statisticsDatum.Label
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
responderCount, exists := responseCountsMap[datumLabel]
|
2024-04-11 15:51:56 +02:00
|
|
|
if (exists == false){
|
2024-06-07 02:04:13 +02:00
|
|
|
return 0, errors.New("responseCountsMap missing label: " + datumLabel)
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
totalRespondersCount += float64(responderCount)
|
|
|
|
|
|
|
|
if (valueIsAverage == true){
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
yAxisAttributeResponsesSum, exists := responseSumsMap[datumLabel]
|
2024-04-11 15:51:56 +02:00
|
|
|
if (exists == false){
|
2024-06-07 02:04:13 +02:00
|
|
|
return 0, errors.New("responseSumsMap missing label: " + datumLabel)
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
allReponsesSummed += yAxisAttributeResponsesSum
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (valueIsAverage == false){
|
|
|
|
|
|
|
|
return totalRespondersCount, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// The value is an average
|
|
|
|
// We need to find the average for all of the user responses for the labels in the input list
|
2024-06-07 02:04:13 +02:00
|
|
|
// The Values in the inputStatisticsDatumsList are averages
|
2024-04-11 15:51:56 +02:00
|
|
|
// We can't average out the averages, because that will not give us the true average
|
2024-06-07 02:04:13 +02:00
|
|
|
// We have to use the original sums for all group datums and average them
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
if (totalRespondersCount == 0){
|
|
|
|
return 0, errors.New("totalRespondersCount is 0.")
|
|
|
|
}
|
|
|
|
|
|
|
|
value := allReponsesSummed/float64(totalRespondersCount)
|
|
|
|
|
|
|
|
return value, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if (labelIsNumerical == true){
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
maximumDatumsPerCategory := int(math.Ceil(float64(len(statisticsDatumsList))/float64(maximumGroupsToCreate)))
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
statisticsDatumsListSublists, err := helpers.SplitListIntoSublists(statisticsDatumsList, maximumDatumsPerCategory)
|
2024-04-11 15:51:56 +02:00
|
|
|
if (err != nil) { return nil, err }
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
groupedDatumsList := make([]statisticsDatum.StatisticsDatum, 0, len(statisticsDatumsListSublists))
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
for _, groupDatumsListSublist := range statisticsDatumsListSublists{
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
if (len(groupDatumsListSublist) == 1){
|
|
|
|
// Sometimes, a group with 1 datum will be created
|
2024-04-11 15:51:56 +02:00
|
|
|
// This happens if the groups cannot be evenly divided, so there is a remainder of 1.
|
|
|
|
// Example: 10->4 groups = 3, 3, 3, 1.
|
2024-06-07 02:04:13 +02:00
|
|
|
//TODO: Prevent this from happening so groups always have more than 1 subdatum
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
groupDatum := groupDatumsListSublist[0]
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
groupedDatumsList = append(groupedDatumsList, groupDatum)
|
2024-04-11 15:51:56 +02:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
finalIndex := len(groupDatumsListSublist)-1
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
initialDatum := groupDatumsListSublist[0]
|
|
|
|
finalDatum := groupDatumsListSublist[finalIndex]
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
initialLabel := initialDatum.Label
|
|
|
|
initialLabelFormatted := initialDatum.LabelFormatted
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
finalLabel := finalDatum.Label
|
|
|
|
finalLabelFormatted := finalDatum.LabelFormatted
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
groupValue, err := getGroupValue(groupDatumsListSublist)
|
2024-04-11 15:51:56 +02:00
|
|
|
if (err != nil) { return nil, err }
|
|
|
|
|
|
|
|
groupValueFormatted, err := formatValuesFunction(groupValue)
|
|
|
|
if (err != nil) { return nil, err }
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
newGroupStatisticsDatum := statisticsDatum.StatisticsDatum{
|
2024-04-11 15:51:56 +02:00
|
|
|
Label: initialLabel + "-" + finalLabel,
|
|
|
|
LabelFormatted: initialLabelFormatted + "-" + finalLabelFormatted,
|
|
|
|
Value: groupValue,
|
|
|
|
ValueFormatted: groupValueFormatted,
|
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
groupedDatumsList = append(groupedDatumsList, newGroupStatisticsDatum)
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
return groupedDatumsList, nil
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Label is not numerical
|
|
|
|
// We combine all categories after the first maximumGroupsToCreate into a category called Other
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
datumsToKeep := statisticsDatumsList[:maximumGroupsToCreate]
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
datumsToCombine := statisticsDatumsList[maximumGroupsToCreate:]
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
otherTranslated := translation.TranslateTextFromEnglishToMyLanguage("Other")
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
otherGroupValue, err := getGroupValue(datumsToCombine)
|
2024-04-11 15:51:56 +02:00
|
|
|
if (err != nil) { return nil, err }
|
|
|
|
|
|
|
|
otherGroupValueFormatted, err := formatValuesFunction(otherGroupValue)
|
|
|
|
if (err != nil) { return nil, err }
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
otherGroupDatum := statisticsDatum.StatisticsDatum{
|
2024-04-11 15:51:56 +02:00
|
|
|
Label: "Other",
|
|
|
|
LabelFormatted: otherTranslated,
|
|
|
|
Value: otherGroupValue,
|
|
|
|
ValueFormatted: otherGroupValueFormatted,
|
|
|
|
}
|
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
groupedStatisticsDatumsList := append(datumsToKeep, otherGroupDatum)
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-07 02:04:13 +02:00
|
|
|
return groupedStatisticsDatumsList, nil
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func getUserIdentityHashesToAnalyzeList(identityType string)([][16]byte, error){
|
|
|
|
|
|
|
|
allUserIdentityHashesList, err := badgerDatabase.GetAllProfileIdentityHashes(identityType)
|
|
|
|
if (err != nil) { return nil, err }
|
|
|
|
|
|
|
|
if (len(allUserIdentityHashesList) < 10000){
|
|
|
|
return allUserIdentityHashesList, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
helpers.RandomizeListOrder(allUserIdentityHashesList)
|
|
|
|
|
|
|
|
identityHashesToAnalyzeList := allUserIdentityHashesList[:10000]
|
|
|
|
|
|
|
|
return identityHashesToAnalyzeList, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
//Outputs:
|
|
|
|
// -bool: Profile Exists
|
|
|
|
// -func(string)(bool, int, string, error)
|
|
|
|
// -error
|
|
|
|
func getRetrieveAnyAttributeFromUserNewestProfileFunction(identityHash [16]byte, networkType byte)(bool, func(string)(bool, int, string, error), error){
|
|
|
|
|
|
|
|
newestProfileExists, profileVersion, _, _, _, newestProfileMap, err := profileStorage.GetNewestUserProfile(identityHash, networkType)
|
|
|
|
if (err != nil) { return false, nil, err }
|
|
|
|
if (newestProfileExists == false){
|
|
|
|
return false, nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
getAnyAttributeFromUserNewestProfileFunction, err := calculatedAttributes.GetRetrieveAnyProfileAttributeIncludingCalculatedFunction(profileVersion, newestProfileMap)
|
|
|
|
if (err != nil) { return false, nil, err }
|
|
|
|
|
|
|
|
return true, getAnyAttributeFromUserNewestProfileFunction, nil
|
|
|
|
}
|
|
|
|
|