From 8ad087e1ca9f9bff03511fdae4a771a3f2dea4d0 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Thu, 23 Nov 2017 17:10:17 +0200 Subject: [PATCH] Player Stickiness calculations --- .../djrapitops/plan/data/AnalysisData.java | 146 +++++++++++++++++- .../plan/utilities/analysis/MathUtils.java | 7 + 2 files changed, 147 insertions(+), 6 deletions(-) diff --git a/Plan/src/main/java/com/djrapitops/plan/data/AnalysisData.java b/Plan/src/main/java/com/djrapitops/plan/data/AnalysisData.java index 8fb0ffeb1..a8644cb89 100644 --- a/Plan/src/main/java/com/djrapitops/plan/data/AnalysisData.java +++ b/Plan/src/main/java/com/djrapitops/plan/data/AnalysisData.java @@ -1,8 +1,10 @@ package main.java.com.djrapitops.plan.data; import com.djrapitops.plugin.api.TimeAmount; +import com.google.common.base.Objects; import main.java.com.djrapitops.plan.Settings; import main.java.com.djrapitops.plan.data.time.WorldTimes; +import main.java.com.djrapitops.plan.database.tables.Actions; import main.java.com.djrapitops.plan.systems.webserver.theme.Colors; import main.java.com.djrapitops.plan.utilities.FormatUtils; import main.java.com.djrapitops.plan.utilities.MiscUtils; @@ -17,10 +19,7 @@ import main.java.com.djrapitops.plan.utilities.html.tables.CommandUseTableCreato import main.java.com.djrapitops.plan.utilities.html.tables.SessionsTableCreator; import java.io.Serializable; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; +import java.util.*; import java.util.stream.Collectors; /** @@ -117,6 +116,7 @@ public class AnalysisData extends RawData { sessionData(monthAgo, sessions, allSessions); onlineActivityNumbers(profile, sessions, players); geolocationsTab(geoLocations); + commandUsage(commandUsage); addValue("ops", ops.size()); addValue("playersTotal", playersTotal); @@ -128,8 +128,6 @@ public class AnalysisData extends RawData { addValue("joinLeaver", 0); addValue("banned", 0); - commandUsage(commandUsage); - addValue("playtimeTotal", FormatUtils.formatTimeAmount(profile.getTotalPlaytime())); addValue("playtimeAverage", FormatUtils.formatTimeAmount(profile.getAveragePlayTime())); } @@ -197,6 +195,86 @@ public class AnalysisData extends RawData { addValue("playersNewAverageDay", AnalysisUtils.getNewUsersPerDay(toRegistered(newDay), -1, newD)); addValue("playersNewAverageWeek", AnalysisUtils.getNewUsersPerDay(toRegistered(newWeek), -1, newW)); addValue("playersNewAverageMonth", AnalysisUtils.getNewUsersPerDay(toRegistered(newMonth), -1, newM)); + + stickiness(now, weekAgo, monthAgo, newDay, newWeek, newMonth, newD, newW, newM); + } + + private void stickiness(long now, long weekAgo, long monthAgo, + List newDay, List newWeek, List newMonth, + int newD, int newW, int newM) { + long fourDaysAgo = now - TimeAmount.DAY.ms() * 4L; + long twoWeeksAgo = now - TimeAmount.WEEK.ms() * 2L; + + List playersStuckPerMonth = newMonth.stream() + .filter(p -> p.playedBetween(monthAgo, twoWeeksAgo) && p.playedBetween(twoWeeksAgo, now)) + .collect(Collectors.toList()); + List playersStuckPerWeek = newWeek.stream() + .filter(p -> p.playedBetween(weekAgo, fourDaysAgo) && p.playedBetween(fourDaysAgo, now)) + .collect(Collectors.toList()); + + int stuckPerM = playersStuckPerMonth.size(); + int stuckPerW = playersStuckPerWeek.size(); + + addValue("playersStuckMonth", stuckPerM); + addValue("playersStuckWeek", stuckPerW); + addValue("playersStuckPercMonth", newM != 0 ? FormatUtils.cutDecimals(MathUtils.averageDouble(stuckPerM, newM)) + "%" : "-"); + addValue("playersStuckPercWeek", newW != 0 ? FormatUtils.cutDecimals(MathUtils.averageDouble(stuckPerW, newW)) + "%" : "-"); + + if (newD != 0) { + // New Players + Set stickyM = newMonth.stream().map(StickyData::new).distinct().collect(Collectors.toSet()); + Set stickyW = playersStuckPerMonth.stream().map(StickyData::new).distinct().collect(Collectors.toSet()); + // New Players who stayed + Set stickyStuckM = newMonth.stream().map(StickyData::new).distinct().collect(Collectors.toSet()); + Set stickyStuckW = playersStuckPerWeek.stream().map(StickyData::new).distinct().collect(Collectors.toSet()); + + int stuckPerD = 0; + for (PlayerProfile playerProfile : newDay) { + StickyData data = new StickyData(playerProfile); + + Set similarM = new HashSet<>(); + Set similarW = new HashSet<>(); + for (StickyData stickyData : stickyM) { + if (stickyData.distance(data) < 2.5) { + similarM.add(stickyData); + } + } + for (StickyData stickyData : stickyW) { + if (stickyData.distance(data) < 2.5) { + similarW.add(stickyData); + } + } + + double probability = 1.0; + + int stickM = 0; + for (StickyData stickyData : stickyStuckM) { + if (similarM.contains(stickyData)) { + stickM++; + } + } + + probability *= (stickM / similarM.size()); + + int stickW = 0; + for (StickyData stickyData : stickyStuckW) { + if (similarW.contains(stickyData)) { + stickW++; + } + } + + probability *= (stickW / similarW.size()); + + if (probability >= 0.5) { + stuckPerD++; + } + } + addValue("playersStuckDay", stuckPerD); + addValue("playersStuckPercDay", FormatUtils.cutDecimals(MathUtils.averageDouble(stuckPerD, newD)) + "%"); + } else { + addValue("playersStuckDay", 0); + addValue("playersStuckPercDay", "-"); + } } private List toRegistered(List players) { @@ -276,3 +354,59 @@ public class AnalysisData extends RawData { addValue("chunkAverageDay", FormatUtils.cutDecimals(MathUtils.averageInt(tpsDataDay.stream().map(TPS::getChunksLoaded).filter(i -> i != 0)))); } } + +class StickyData { + private final double activityIndex; + private Integer messagesSent; + private Integer onlineOnJoin; + + public StickyData(PlayerProfile player) { + activityIndex = player.getActivityIndex(player.getRegistered() + TimeAmount.DAY.ms()); + for (Action action : player.getActions()) { + if (messagesSent == null && action.getDoneAction() == Actions.FIRST_LOGOUT) { + String additionalInfo = action.getAdditionalInfo(); + String[] split = additionalInfo.split(": "); + if (split.length == 2) { + try { + messagesSent = Integer.parseInt(split[1]); + } catch (NumberFormatException ignored) { + } + } + } + if (onlineOnJoin == null && action.getDoneAction() == Actions.FIRST_SESSION) { + String additionalInfo = action.getAdditionalInfo(); + String[] split = additionalInfo.split(" "); + if (split.length == 3) { + try { + onlineOnJoin = Integer.parseInt(split[1]); + } catch (NumberFormatException ignored) { + } + } + } + } + } + + public double distance(StickyData data) { + double num = 0; + num += Math.abs(data.activityIndex - activityIndex) * 2.0; + num += Math.abs(data.onlineOnJoin - onlineOnJoin) / 10.0; + num += Math.abs(data.messagesSent - messagesSent) / 10.0; + + return num; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + StickyData that = (StickyData) o; + return Double.compare(that.activityIndex, activityIndex) == 0 && + Objects.equal(messagesSent, that.messagesSent) && + Objects.equal(onlineOnJoin, that.onlineOnJoin); + } + + @Override + public int hashCode() { + return Objects.hashCode(activityIndex, messagesSent, onlineOnJoin); + } +} diff --git a/Plan/src/main/java/com/djrapitops/plan/utilities/analysis/MathUtils.java b/Plan/src/main/java/com/djrapitops/plan/utilities/analysis/MathUtils.java index 0f1e9ef74..ceaca156a 100644 --- a/Plan/src/main/java/com/djrapitops/plan/utilities/analysis/MathUtils.java +++ b/Plan/src/main/java/com/djrapitops/plan/utilities/analysis/MathUtils.java @@ -191,4 +191,11 @@ public class MathUtils { public static double round(double number) { return Double.valueOf(decimalFormat.format(number)); } + + public static double averageDouble(double amount, double size) { + if (size == 0) { + return -1; + } + return amount / size; + } }