mirror of
https://github.com/plan-player-analytics/Plan.git
synced 2024-12-25 18:47:50 +01:00
Player retention
This commit is contained in:
parent
7e420aaf91
commit
566f838a3a
@ -2,10 +2,10 @@ package com.djrapitops.plan.command.commands;
|
||||
|
||||
import com.djrapitops.plan.PlanPlugin;
|
||||
import com.djrapitops.plan.api.exceptions.database.DBOpException;
|
||||
import com.djrapitops.plan.data.calculation.ActivityIndex;
|
||||
import com.djrapitops.plan.data.container.GeoInfo;
|
||||
import com.djrapitops.plan.data.store.containers.PlayerContainer;
|
||||
import com.djrapitops.plan.data.store.keys.PlayerKeys;
|
||||
import com.djrapitops.plan.data.store.mutators.ActivityIndex;
|
||||
import com.djrapitops.plan.data.store.mutators.GeoInfoMutator;
|
||||
import com.djrapitops.plan.data.store.mutators.SessionsMutator;
|
||||
import com.djrapitops.plan.data.store.mutators.formatting.Formatter;
|
||||
|
@ -4,11 +4,11 @@
|
||||
*/
|
||||
package com.djrapitops.plan.data;
|
||||
|
||||
import com.djrapitops.plan.data.calculation.ActivityIndex;
|
||||
import com.djrapitops.plan.data.container.Action;
|
||||
import com.djrapitops.plan.data.container.GeoInfo;
|
||||
import com.djrapitops.plan.data.container.PlayerKill;
|
||||
import com.djrapitops.plan.data.container.Session;
|
||||
import com.djrapitops.plan.data.store.mutators.ActivityIndex;
|
||||
import com.djrapitops.plan.data.time.WorldTimes;
|
||||
import com.djrapitops.plan.system.info.server.ServerInfo;
|
||||
import com.djrapitops.plan.utilities.comparators.ActionComparator;
|
||||
|
@ -4,10 +4,10 @@ import com.djrapitops.plan.PlanPlugin;
|
||||
import com.djrapitops.plan.data.PlayerProfile;
|
||||
import com.djrapitops.plan.data.ServerProfile;
|
||||
import com.djrapitops.plan.data.container.Session;
|
||||
import com.djrapitops.plan.data.container.StickyData;
|
||||
import com.djrapitops.plan.data.container.TPS;
|
||||
import com.djrapitops.plan.data.element.AnalysisContainer;
|
||||
import com.djrapitops.plan.data.plugin.PluginData;
|
||||
import com.djrapitops.plan.data.store.mutators.RetentionData;
|
||||
import com.djrapitops.plan.data.store.mutators.SessionsMutator;
|
||||
import com.djrapitops.plan.data.store.mutators.formatting.Formatters;
|
||||
import com.djrapitops.plan.data.time.WorldTimes;
|
||||
@ -55,7 +55,7 @@ public class AnalysisData extends RawData {
|
||||
private long refreshDate;
|
||||
|
||||
private Map<String, Long> analyzedValues;
|
||||
private Set<StickyData> stickyMonthData;
|
||||
private Set<RetentionData> stickyMonthData;
|
||||
private List<PlayerProfile> players;
|
||||
|
||||
public AnalysisData() {
|
||||
@ -250,7 +250,7 @@ public class AnalysisData extends RawData {
|
||||
got("stuckPerM", stuckPerM);
|
||||
got("stuckPerW", stuckPerW);
|
||||
|
||||
stickyMonthData = newMonth.stream().map(StickyData::new).distinct().collect(Collectors.toSet());
|
||||
stickyMonthData = newMonth.stream().map(RetentionData::new).distinct().collect(Collectors.toSet());
|
||||
|
||||
addValue("playersStuckMonth", stuckPerM);
|
||||
addValue("playersStuckWeek", stuckPerW);
|
||||
@ -288,16 +288,16 @@ public class AnalysisData extends RawData {
|
||||
return;
|
||||
}
|
||||
|
||||
List<StickyData> stuck = stuckAfterMonth.stream().map(StickyData::new).collect(Collectors.toList());
|
||||
List<StickyData> nonStuck = notStuckAfterMonth.stream().map(StickyData::new).collect(Collectors.toList());
|
||||
List<RetentionData> stuck = stuckAfterMonth.stream().map(RetentionData::new).collect(Collectors.toList());
|
||||
List<RetentionData> nonStuck = notStuckAfterMonth.stream().map(RetentionData::new).collect(Collectors.toList());
|
||||
|
||||
StickyData avgStuck = AnalysisUtils.average(stuck);
|
||||
StickyData avgNonStuck = AnalysisUtils.average(nonStuck);
|
||||
RetentionData avgStuck = AnalysisUtils.average(stuck);
|
||||
RetentionData avgNonStuck = AnalysisUtils.average(nonStuck);
|
||||
|
||||
int stuckPerD = 0;
|
||||
for (PlayerProfile player : newDay) {
|
||||
StickyData stickyData = new StickyData(player);
|
||||
if (stickyData.distance(avgStuck) < stickyData.distance(avgNonStuck)) {
|
||||
RetentionData retentionData = new RetentionData(player);
|
||||
if (retentionData.distance(avgStuck) < retentionData.distance(avgNonStuck)) {
|
||||
stuckPerD++;
|
||||
}
|
||||
}
|
||||
@ -403,7 +403,7 @@ public class AnalysisData extends RawData {
|
||||
return analyzedValues.getOrDefault(key, 0L);
|
||||
}
|
||||
|
||||
public Set<StickyData> getStickyMonthData() {
|
||||
public Set<RetentionData> getStickyMonthData() {
|
||||
return stickyMonthData;
|
||||
}
|
||||
|
||||
|
@ -6,8 +6,8 @@ package com.djrapitops.plan.data.calculation;
|
||||
|
||||
import com.djrapitops.plan.data.PlayerProfile;
|
||||
import com.djrapitops.plan.data.ServerProfile;
|
||||
import com.djrapitops.plan.data.container.StickyData;
|
||||
import com.djrapitops.plan.data.container.TPS;
|
||||
import com.djrapitops.plan.data.store.mutators.RetentionData;
|
||||
import com.djrapitops.plan.data.store.mutators.formatting.Formatter;
|
||||
import com.djrapitops.plan.data.store.mutators.formatting.Formatters;
|
||||
import com.djrapitops.plan.system.settings.Settings;
|
||||
@ -128,7 +128,7 @@ public class HealthNotes {
|
||||
}
|
||||
|
||||
private void newPlayerNote() {
|
||||
double avgOnlineOnRegister = MathUtils.averageDouble(analysisData.getStickyMonthData().stream().map(StickyData::getOnlineOnJoin));
|
||||
double avgOnlineOnRegister = MathUtils.averageDouble(analysisData.getStickyMonthData().stream().map(RetentionData::getOnlineOnJoin));
|
||||
if (avgOnlineOnRegister >= 1) {
|
||||
notes.add("<p>" + Html.GREEN_THUMB.parse() + " New Players have players to play with when they join ("
|
||||
+ FormatUtils.cutDecimals(avgOnlineOnRegister) + " on average)</p>");
|
||||
|
@ -1,108 +0,0 @@
|
||||
/*
|
||||
* Licence is provided in the jar as license.yml also here:
|
||||
* https://github.com/Rsl1122/Plan-PlayerAnalytics/blob/master/Plan/src/main/resources/license.yml
|
||||
*/
|
||||
package com.djrapitops.plan.data.container;
|
||||
|
||||
import com.djrapitops.plan.data.Actions;
|
||||
import com.djrapitops.plan.data.PlayerProfile;
|
||||
import com.djrapitops.plugin.api.TimeAmount;
|
||||
import com.google.common.base.Objects;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class StickyData {
|
||||
private final double activityIndex;
|
||||
private Double messagesSent;
|
||||
private Double onlineOnJoin;
|
||||
|
||||
public StickyData(PlayerProfile player) {
|
||||
activityIndex = player.getActivityIndex(player.getRegistered() + TimeAmount.DAY.ms()).getValue();
|
||||
loadActionVariables(player.getActions());
|
||||
}
|
||||
|
||||
public StickyData(double activityIndex, Double messagesSent, Double onlineOnJoin) {
|
||||
this.activityIndex = activityIndex;
|
||||
this.messagesSent = messagesSent;
|
||||
this.onlineOnJoin = onlineOnJoin;
|
||||
}
|
||||
|
||||
private void loadActionVariables(List<Action> actions) {
|
||||
for (Action action : actions) {
|
||||
try {
|
||||
if (messagesSent == null && action.getDoneAction() == Actions.FIRST_LOGOUT) {
|
||||
messagesSent = (double) loadSentMessages(action);
|
||||
}
|
||||
if (onlineOnJoin == null && action.getDoneAction() == Actions.FIRST_SESSION) {
|
||||
onlineOnJoin = (double) loadOnlineOnJoin(action);
|
||||
}
|
||||
} catch (IllegalArgumentException ignore) {
|
||||
/* continue */
|
||||
}
|
||||
}
|
||||
setDefaultValuesIfNull();
|
||||
}
|
||||
|
||||
private void setDefaultValuesIfNull() {
|
||||
if (messagesSent == null) {
|
||||
messagesSent = 0.0;
|
||||
}
|
||||
if (onlineOnJoin == null) {
|
||||
onlineOnJoin = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
private int loadOnlineOnJoin(Action action) {
|
||||
String additionalInfo = action.getAdditionalInfo();
|
||||
String[] split = additionalInfo.split(" ");
|
||||
if (split.length == 3) {
|
||||
return Integer.parseInt(split[1]);
|
||||
}
|
||||
throw new IllegalArgumentException("Improper Action");
|
||||
}
|
||||
|
||||
private int loadSentMessages(Action action) {
|
||||
String additionalInfo = action.getAdditionalInfo();
|
||||
String[] split = additionalInfo.split(": ");
|
||||
if (split.length == 2) {
|
||||
return Integer.parseInt(split[1]);
|
||||
}
|
||||
throw new IllegalArgumentException("Improper Action");
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public double getOnlineOnJoin() {
|
||||
return onlineOnJoin;
|
||||
}
|
||||
|
||||
public double getActivityIndex() {
|
||||
return activityIndex;
|
||||
}
|
||||
|
||||
public Double getMessagesSent() {
|
||||
return messagesSent;
|
||||
}
|
||||
}
|
@ -5,10 +5,7 @@ import com.djrapitops.plan.data.store.Key;
|
||||
import com.djrapitops.plan.data.store.keys.AnalysisKeys;
|
||||
import com.djrapitops.plan.data.store.keys.PlayerKeys;
|
||||
import com.djrapitops.plan.data.store.keys.ServerKeys;
|
||||
import com.djrapitops.plan.data.store.mutators.CommandUseMutator;
|
||||
import com.djrapitops.plan.data.store.mutators.PlayersMutator;
|
||||
import com.djrapitops.plan.data.store.mutators.SessionsMutator;
|
||||
import com.djrapitops.plan.data.store.mutators.TPSMutator;
|
||||
import com.djrapitops.plan.data.store.mutators.*;
|
||||
import com.djrapitops.plan.data.store.mutators.formatting.Formatters;
|
||||
import com.djrapitops.plan.data.time.WorldTimes;
|
||||
import com.djrapitops.plan.system.database.databases.Database;
|
||||
@ -32,10 +29,7 @@ import com.djrapitops.plan.utilities.html.tables.PlayersTable;
|
||||
import com.djrapitops.plan.utilities.html.tables.ServerSessionTable;
|
||||
import com.djrapitops.plugin.api.TimeAmount;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@ -172,6 +166,28 @@ public class AnalysisContainer extends DataContainer {
|
||||
putSupplier(AnalysisKeys.AVG_PLAYERS_NEW_DAY, () -> getUnsafe(uniqueDay).newPerDay());
|
||||
putSupplier(AnalysisKeys.AVG_PLAYERS_NEW_WEEK, () -> getUnsafe(uniqueWeek).newPerDay());
|
||||
putSupplier(AnalysisKeys.AVG_PLAYERS_NEW_MONTH, () -> getUnsafe(uniqueMonth).newPerDay());
|
||||
|
||||
Key<Integer> retentionDay = new Key<>(Integer.class, "RETENTION_DAY");
|
||||
// compareAndFindThoseLikelyToBeRetained can throw exception.
|
||||
putSupplier(retentionDay, () -> getUnsafe(AnalysisKeys.PLAYERS_MUTATOR).compareAndFindThoseLikelyToBeRetained(
|
||||
getUnsafe(newDay).all(), getUnsafe(AnalysisKeys.ANALYSIS_TIME_MONTH_AGO),
|
||||
getUnsafe(AnalysisKeys.PLAYERS_ONLINE_RESOLVER)
|
||||
).count()
|
||||
);
|
||||
putSupplier(AnalysisKeys.PLAYERS_RETAINED_DAY, () -> {
|
||||
try {
|
||||
return getUnsafe(retentionDay);
|
||||
} catch (IllegalStateException e) {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
putSupplier(AnalysisKeys.PLAYERS_RETAINED_DAY_PERC, () -> {
|
||||
try {
|
||||
return Formatters.percentage().apply(1.0 * getUnsafe(retentionDay) / getUnsafe(AnalysisKeys.PLAYERS_NEW_DAY));
|
||||
} catch (IllegalStateException e) {
|
||||
return "Not enough data";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void addSessionSuppliers() {
|
||||
@ -228,6 +244,24 @@ public class AnalysisContainer extends DataContainer {
|
||||
putSupplier(AnalysisKeys.AVG_PLAYERS_DAY, () -> getUnsafe(sessionsDay).toUniqueJoinsPerDay());
|
||||
putSupplier(AnalysisKeys.AVG_PLAYERS_WEEK, () -> getUnsafe(sessionsWeek).toUniqueJoinsPerDay());
|
||||
putSupplier(AnalysisKeys.AVG_PLAYERS_MONTH, () -> getUnsafe(sessionsMonth).toUniqueJoinsPerDay());
|
||||
putSupplier(AnalysisKeys.PLAYERS_RETAINED_WEEK, () ->
|
||||
PlayersMutator.copyOf(getUnsafe(AnalysisKeys.PLAYERS_MUTATOR)).filterRetained(
|
||||
getUnsafe(AnalysisKeys.ANALYSIS_TIME_WEEK_AGO),
|
||||
getUnsafe(AnalysisKeys.ANALYSIS_TIME)
|
||||
).count()
|
||||
);
|
||||
putSupplier(AnalysisKeys.PLAYERS_RETAINED_MONTH, () ->
|
||||
PlayersMutator.copyOf(getUnsafe(AnalysisKeys.PLAYERS_MUTATOR)).filterRetained(
|
||||
getUnsafe(AnalysisKeys.ANALYSIS_TIME_MONTH_AGO),
|
||||
getUnsafe(AnalysisKeys.ANALYSIS_TIME)
|
||||
).count()
|
||||
);
|
||||
putSupplier(AnalysisKeys.PLAYERS_RETAINED_WEEK_PERC, () -> Formatters.percentage().apply(
|
||||
1.0 * getUnsafe(AnalysisKeys.PLAYERS_RETAINED_WEEK) / getUnsafe(AnalysisKeys.PLAYERS_NEW_WEEK))
|
||||
);
|
||||
putSupplier(AnalysisKeys.PLAYERS_RETAINED_MONTH_PERC, () -> Formatters.percentage().apply(
|
||||
1.0 * getUnsafe(AnalysisKeys.PLAYERS_RETAINED_MONTH) / getUnsafe(AnalysisKeys.PLAYERS_NEW_MONTH))
|
||||
);
|
||||
}
|
||||
|
||||
private void addGraphSuppliers() {
|
||||
@ -256,6 +290,13 @@ public class AnalysisContainer extends DataContainer {
|
||||
putSupplier(AnalysisKeys.ACTIVITY_PIE_SERIES, () ->
|
||||
new ActivityPie(getUnsafe(AnalysisKeys.ACTIVITY_DATA).get(getUnsafe(AnalysisKeys.ANALYSIS_TIME))).toHighChartsSeries()
|
||||
);
|
||||
putSupplier(AnalysisKeys.PLAYERS_REGULAR, () -> {
|
||||
Map<String, Set<UUID>> activityNow = getUnsafe(AnalysisKeys.ACTIVITY_DATA).get(getUnsafe(AnalysisKeys.ANALYSIS_TIME));
|
||||
Set<UUID> veryActiveNow = activityNow.getOrDefault("Very Active", new HashSet<>());
|
||||
Set<UUID> activeNow = activityNow.getOrDefault("Active", new HashSet<>());
|
||||
Set<UUID> regularNow = activityNow.getOrDefault("Regular", new HashSet<>());
|
||||
return veryActiveNow.size() + activeNow.size() + regularNow.size();
|
||||
});
|
||||
}
|
||||
|
||||
private void addTPSAverageSuppliers() {
|
||||
@ -273,6 +314,8 @@ public class AnalysisContainer extends DataContainer {
|
||||
.filterDataBetween(getUnsafe(AnalysisKeys.ANALYSIS_TIME_DAY_AGO), getUnsafe(AnalysisKeys.ANALYSIS_TIME))
|
||||
);
|
||||
|
||||
putSupplier(AnalysisKeys.PLAYERS_ONLINE_RESOLVER, () -> new PlayersOnlineResolver(getUnsafe(AnalysisKeys.TPS_MUTATOR)));
|
||||
|
||||
putSupplier(AnalysisKeys.TPS_SPIKE_MONTH, () -> getUnsafe(tpsMonth).lowTpsSpikeCount());
|
||||
putSupplier(AnalysisKeys.AVG_TPS_MONTH, () -> getUnsafe(tpsMonth).averageTPS());
|
||||
putSupplier(AnalysisKeys.AVG_CPU_MONTH, () -> getUnsafe(tpsMonth).averageCPU());
|
||||
|
@ -1,5 +1,7 @@
|
||||
package com.djrapitops.plan.data.store.containers;
|
||||
|
||||
import com.djrapitops.plan.data.store.mutators.SessionsMutator;
|
||||
|
||||
/**
|
||||
* DataContainer about a Player.
|
||||
* <p>
|
||||
@ -9,4 +11,9 @@ package com.djrapitops.plan.data.store.containers;
|
||||
* @see com.djrapitops.plan.data.store.keys.PlayerKeys For Key objects.
|
||||
*/
|
||||
public class PlayerContainer extends DataContainer {
|
||||
|
||||
public boolean playedBetween(long after, long before) {
|
||||
return SessionsMutator.forContainer(this).playedBetween(after, before);
|
||||
}
|
||||
|
||||
}
|
@ -4,6 +4,7 @@ import com.djrapitops.plan.data.store.Key;
|
||||
import com.djrapitops.plan.data.store.PlaceholderKey;
|
||||
import com.djrapitops.plan.data.store.Type;
|
||||
import com.djrapitops.plan.data.store.mutators.PlayersMutator;
|
||||
import com.djrapitops.plan.data.store.mutators.PlayersOnlineResolver;
|
||||
import com.djrapitops.plan.data.store.mutators.SessionsMutator;
|
||||
import com.djrapitops.plan.data.store.mutators.TPSMutator;
|
||||
|
||||
@ -86,12 +87,12 @@ public class AnalysisKeys {
|
||||
public static final PlaceholderKey<Integer> AVG_PLAYERS_NEW_DAY = new PlaceholderKey<>(Integer.class, "playersNewAverageDay");
|
||||
public static final PlaceholderKey<Integer> AVG_PLAYERS_NEW_WEEK = new PlaceholderKey<>(Integer.class, "playersNewAverageWeek");
|
||||
public static final PlaceholderKey<Integer> AVG_PLAYERS_NEW_MONTH = new PlaceholderKey<>(Integer.class, "playersNewAverageMonth");
|
||||
public static final PlaceholderKey<Integer> PLAYERS_STUCK_DAY = new PlaceholderKey<>(Integer.class, "playersStuckDay");
|
||||
public static final PlaceholderKey<String> PLAYERS_STUCK_DAY_PERC = new PlaceholderKey<>(String.class, "playersStuckPercDay");
|
||||
public static final PlaceholderKey<Integer> PLAYERS_STUCK_WEEK = new PlaceholderKey<>(Integer.class, "playersStuckWeek");
|
||||
public static final PlaceholderKey<String> PLAYERS_STUCK_WEEK_PERC = new PlaceholderKey<>(String.class, "playersStuckPercWeek");
|
||||
public static final PlaceholderKey<Integer> PLAYERS_STUCK_MONTH = new PlaceholderKey<>(Integer.class, "playersStuckMonth");
|
||||
public static final PlaceholderKey<String> PLAYERS_STUCK_MONTH_PERC = new PlaceholderKey<>(String.class, "playersStuckPercMonth");
|
||||
public static final PlaceholderKey<Integer> PLAYERS_RETAINED_DAY = new PlaceholderKey<>(Integer.class, "playersStuckDay");
|
||||
public static final PlaceholderKey<String> PLAYERS_RETAINED_DAY_PERC = new PlaceholderKey<>(String.class, "playersStuckPercDay");
|
||||
public static final PlaceholderKey<Integer> PLAYERS_RETAINED_WEEK = new PlaceholderKey<>(Integer.class, "playersStuckWeek");
|
||||
public static final PlaceholderKey<String> PLAYERS_RETAINED_WEEK_PERC = new PlaceholderKey<>(String.class, "playersStuckPercWeek");
|
||||
public static final PlaceholderKey<Integer> PLAYERS_RETAINED_MONTH = new PlaceholderKey<>(Integer.class, "playersStuckMonth");
|
||||
public static final PlaceholderKey<String> PLAYERS_RETAINED_MONTH_PERC = new PlaceholderKey<>(String.class, "playersStuckPercMonth");
|
||||
//
|
||||
public static final PlaceholderKey<Integer> TPS_SPIKE_MONTH = new PlaceholderKey<>(Integer.class, "tpsSpikeMonth");
|
||||
public static final PlaceholderKey<Integer> TPS_SPIKE_WEEK = new PlaceholderKey<>(Integer.class, "tpsSpikeWeek");
|
||||
@ -130,6 +131,7 @@ public class AnalysisKeys {
|
||||
public static final Key<SessionsMutator> SESSIONS_MUTATOR = new Key<>(SessionsMutator.class, "SESSIONS_MUTATOR");
|
||||
public static final Key<TPSMutator> TPS_MUTATOR = new Key<>(TPSMutator.class, "TPS_MUTATOR");
|
||||
public static final Key<PlayersMutator> PLAYERS_MUTATOR = new Key<>(PlayersMutator.class, "PLAYERS_MUTATOR");
|
||||
public static final Key<PlayersOnlineResolver> PLAYERS_ONLINE_RESOLVER = new Key<>(PlayersOnlineResolver.class, "PLAYERS_ONLINE_RESOLVER");
|
||||
public static final Key<Long> PLAYTIME_TOTAL = new Key<>(Long.class, "PLAYTIME_TOTAL");
|
||||
public static final Key<Long> ANALYSIS_TIME = new Key<>(Long.class, "ANALYSIS_TIME");
|
||||
public static final Key<Long> ANALYSIS_TIME_DAY_AGO = new Key<>(Long.class, "ANALYSIS_TIME_DAY_AGO");
|
||||
|
@ -1,7 +1,6 @@
|
||||
package com.djrapitops.plan.data.calculation;
|
||||
package com.djrapitops.plan.data.store.mutators;
|
||||
|
||||
import com.djrapitops.plan.data.store.containers.DataContainer;
|
||||
import com.djrapitops.plan.data.store.mutators.SessionsMutator;
|
||||
import com.djrapitops.plan.system.settings.Settings;
|
||||
import com.djrapitops.plan.utilities.FormatUtils;
|
||||
import com.djrapitops.plugin.api.TimeAmount;
|
@ -1,6 +1,5 @@
|
||||
package com.djrapitops.plan.data.store.mutators;
|
||||
|
||||
import com.djrapitops.plan.data.calculation.ActivityIndex;
|
||||
import com.djrapitops.plan.data.container.GeoInfo;
|
||||
import com.djrapitops.plan.data.store.containers.DataContainer;
|
||||
import com.djrapitops.plan.data.store.containers.PlayerContainer;
|
||||
@ -8,6 +7,7 @@ import com.djrapitops.plan.data.store.keys.PlayerKeys;
|
||||
import com.djrapitops.plan.data.store.keys.ServerKeys;
|
||||
import com.djrapitops.plan.data.store.keys.SessionKeys;
|
||||
import com.djrapitops.plan.data.store.mutators.formatting.Formatters;
|
||||
import com.djrapitops.plan.utilities.analysis.AnalysisUtils;
|
||||
import com.djrapitops.plugin.api.TimeAmount;
|
||||
|
||||
import java.util.*;
|
||||
@ -54,6 +54,19 @@ public class PlayersMutator {
|
||||
return this;
|
||||
}
|
||||
|
||||
public PlayersMutator filterRetained(long after, long before) {
|
||||
players = players.stream()
|
||||
.filter(player -> {
|
||||
long backLimit = Math.max(after, player.getValue(PlayerKeys.REGISTERED).orElse(0L));
|
||||
long half = backLimit + ((before - backLimit) / 2L);
|
||||
SessionsMutator firstHalf = SessionsMutator.forContainer(player);
|
||||
SessionsMutator secondHalf = SessionsMutator.copyOf(firstHalf);
|
||||
return !firstHalf.playedBetween(backLimit, half) && !secondHalf.playedBetween(half, before);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<PlayerContainer> all() {
|
||||
return players;
|
||||
}
|
||||
@ -117,4 +130,59 @@ public class PlayersMutator {
|
||||
}
|
||||
return total / numberOfDays;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares players in the mutator to other players in terms of player retention.
|
||||
*
|
||||
* @param compareTo Players to compare to.
|
||||
* @param dateLimit Epoch ms back limit, if the player registered after this their value is not used.
|
||||
* @return Mutator containing the players that are considered to be retained.
|
||||
* @throws IllegalStateException If all players are rejected due to dateLimit.
|
||||
*/
|
||||
public PlayersMutator compareAndFindThoseLikelyToBeRetained(Iterable<PlayerContainer> compareTo,
|
||||
long dateLimit,
|
||||
PlayersOnlineResolver onlineResolver) {
|
||||
Collection<PlayerContainer> retainedAfterMonth = new ArrayList<>();
|
||||
Collection<PlayerContainer> notRetainedAfterMonth = new ArrayList<>();
|
||||
|
||||
for (PlayerContainer player : players) {
|
||||
long registered = player.getValue(PlayerKeys.REGISTERED).orElse(System.currentTimeMillis());
|
||||
|
||||
// Discard uncertain data
|
||||
if (registered > dateLimit) {
|
||||
continue;
|
||||
}
|
||||
|
||||
long monthAfterRegister = registered + TimeAmount.MONTH.ms();
|
||||
long half = registered + (TimeAmount.MONTH.ms() / 2L);
|
||||
if (player.playedBetween(registered, half) && player.playedBetween(half, monthAfterRegister)) {
|
||||
retainedAfterMonth.add(player);
|
||||
} else {
|
||||
notRetainedAfterMonth.add(player);
|
||||
}
|
||||
}
|
||||
|
||||
if (retainedAfterMonth.isEmpty() || notRetainedAfterMonth.isEmpty()) {
|
||||
throw new IllegalStateException("No players to compare to after rejecting with dateLimit");
|
||||
}
|
||||
|
||||
List<RetentionData> retained = retainedAfterMonth.stream()
|
||||
.map(player -> new RetentionData(player, onlineResolver))
|
||||
.collect(Collectors.toList());
|
||||
List<RetentionData> notRetained = notRetainedAfterMonth.stream()
|
||||
.map(player -> new RetentionData(player, onlineResolver))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
RetentionData avgRetained = AnalysisUtils.average(retained);
|
||||
RetentionData avgNotRetained = AnalysisUtils.average(notRetained);
|
||||
|
||||
List<PlayerContainer> toBeRetained = new ArrayList<>();
|
||||
for (PlayerContainer player : compareTo) {
|
||||
RetentionData retentionData = new RetentionData(player, onlineResolver);
|
||||
if (retentionData.distance(avgRetained) < retentionData.distance(avgNotRetained)) {
|
||||
toBeRetained.add(player);
|
||||
}
|
||||
}
|
||||
return new PlayersMutator(toBeRetained);
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package com.djrapitops.plan.data.store.mutators;
|
||||
|
||||
import com.djrapitops.plan.utilities.html.graphs.line.Point;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Resolves dates into players online numbers with a help of a NavigableMap.
|
||||
* <p>
|
||||
* Time Complexity of O(n / 2) with the use of TreeMap.
|
||||
*
|
||||
* @author Rsl1122
|
||||
*/
|
||||
public class PlayersOnlineResolver {
|
||||
|
||||
private final NavigableMap<Long, Integer> onlineNumberMap;
|
||||
|
||||
public PlayersOnlineResolver(TPSMutator mutator) {
|
||||
List<Point> points = mutator.playersOnlinePoints();
|
||||
onlineNumberMap = new TreeMap<>();
|
||||
for (Point point : points) {
|
||||
double date = point.getX();
|
||||
double value = point.getY();
|
||||
onlineNumberMap.put((long) date, (int) value);
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<Integer> getOnlineOn(long date) {
|
||||
Map.Entry<Long, Integer> entry = onlineNumberMap.floorEntry(date);
|
||||
if (entry == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(entry.getValue());
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Licence is provided in the jar as license.yml also here:
|
||||
* https://github.com/Rsl1122/Plan-PlayerAnalytics/blob/master/Plan/src/main/resources/license.yml
|
||||
*/
|
||||
package com.djrapitops.plan.data.store.mutators;
|
||||
|
||||
import com.djrapitops.plan.data.PlayerProfile;
|
||||
import com.djrapitops.plan.data.store.containers.PlayerContainer;
|
||||
import com.djrapitops.plan.data.store.keys.PlayerKeys;
|
||||
import com.djrapitops.plugin.api.TimeAmount;
|
||||
import com.google.common.base.Objects;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Utility class for player retention calculations.
|
||||
* <p>
|
||||
* Previously known as StickyData.
|
||||
*
|
||||
* @author Rsl1122
|
||||
*/
|
||||
public class RetentionData {
|
||||
private final double activityIndex;
|
||||
private double onlineOnJoin;
|
||||
|
||||
@Deprecated
|
||||
public RetentionData(PlayerProfile player) {
|
||||
activityIndex = player.getActivityIndex(player.getRegistered() + TimeAmount.DAY.ms()).getValue();
|
||||
}
|
||||
|
||||
public RetentionData(double activityIndex, double onlineOnJoin) {
|
||||
this.activityIndex = activityIndex;
|
||||
this.onlineOnJoin = onlineOnJoin;
|
||||
}
|
||||
|
||||
public RetentionData(PlayerContainer player, PlayersOnlineResolver onlineOnJoin) {
|
||||
Optional<Long> registeredValue = player.getValue(PlayerKeys.REGISTERED);
|
||||
activityIndex = registeredValue
|
||||
.map(registered -> new ActivityIndex(player, registered + TimeAmount.DAY.ms()).getValue())
|
||||
.orElse(0.0);
|
||||
this.onlineOnJoin = registeredValue
|
||||
.map(registered -> onlineOnJoin.getOnlineOn(registered).orElse(-1))
|
||||
.orElse(0);
|
||||
}
|
||||
|
||||
public double distance(RetentionData data) {
|
||||
double num = 0;
|
||||
num += Math.abs(data.activityIndex - activityIndex) * 2.0;
|
||||
num += data.onlineOnJoin != -1 && onlineOnJoin != -1
|
||||
? Math.abs(data.onlineOnJoin - onlineOnJoin) / 10.0
|
||||
: 0;
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
RetentionData that = (RetentionData) o;
|
||||
return Double.compare(that.activityIndex, activityIndex) == 0 &&
|
||||
Objects.equal(onlineOnJoin, that.onlineOnJoin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(activityIndex, onlineOnJoin);
|
||||
}
|
||||
|
||||
public double getOnlineOnJoin() {
|
||||
return onlineOnJoin;
|
||||
}
|
||||
|
||||
public double getActivityIndex() {
|
||||
return activityIndex;
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ import com.djrapitops.plan.utilities.analysis.MathUtils;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@ -42,8 +43,7 @@ public class SessionsMutator {
|
||||
|
||||
public SessionsMutator filterSessionsBetween(long after, long before) {
|
||||
sessions = sessions.stream()
|
||||
.filter(session -> after <= session.getValue(SessionKeys.END).orElse(System.currentTimeMillis())
|
||||
&& session.getUnsafe(SessionKeys.START) <= before)
|
||||
.filter(getBetweenPredicate(after, before))
|
||||
.collect(Collectors.toList());
|
||||
return this;
|
||||
}
|
||||
@ -164,4 +164,16 @@ public class SessionsMutator {
|
||||
public int toPlayerKillCount() {
|
||||
return toPlayerKillList().size();
|
||||
}
|
||||
|
||||
public boolean playedBetween(long after, long before) {
|
||||
return sessions.stream().anyMatch(getBetweenPredicate(after, before));
|
||||
}
|
||||
|
||||
private Predicate<Session> getBetweenPredicate(long after, long before) {
|
||||
return session -> {
|
||||
Long start = session.getUnsafe(SessionKeys.START);
|
||||
Long end = session.getValue(SessionKeys.END).orElse(System.currentTimeMillis());
|
||||
return (after <= start && start <= before) || (after <= end && end <= before);
|
||||
};
|
||||
}
|
||||
}
|
@ -25,9 +25,7 @@ public class Formatters {
|
||||
}
|
||||
|
||||
public static Formatter<Long> yearLongValue() {
|
||||
return date -> {
|
||||
return date > 0 ? FormatUtils.formatTimeStampYear(date) : "-";
|
||||
};
|
||||
return date -> date > 0 ? FormatUtils.formatTimeStampYear(date) : "-";
|
||||
}
|
||||
|
||||
public static Formatter<DateHolder> day() {
|
||||
@ -66,4 +64,8 @@ public class Formatters {
|
||||
return day.get(Calendar.DAY_OF_YEAR);
|
||||
};
|
||||
}
|
||||
|
||||
public static Formatter<Double> percentage() {
|
||||
return value -> value >= 0 ? FormatUtils.cutDecimals(value * 100.0) + "%" : "-";
|
||||
}
|
||||
}
|
@ -62,8 +62,8 @@ public class AnalysisPage extends Page {
|
||||
// AVG_PLAYERS, AVG_PLAYERS_DAY, AVG_PLAYERS_WEEK,
|
||||
// AVG_PLAYERS_MONTH, AVG_PLAYERS_NEW, AVG_PLAYERS_NEW_DAY,
|
||||
// AVG_PLAYERS_NEW_WEEK, AVG_PLAYERS_NEW_MONTH, PLAYERS_STUCK_DAY,
|
||||
// PLAYERS_STUCK_DAY_PERC, PLAYERS_STUCK_WEEK, PLAYERS_STUCK_WEEK_PERC,
|
||||
// PLAYERS_STUCK_MONTH, PLAYERS_STUCK_MONTH_PERC,
|
||||
// PLAYERS_STUCK_DAY_PERC, PLAYERS_RETAINED_WEEK, PLAYERS_RETAINED_WEEK_PERC,
|
||||
// PLAYERS_RETAINED_MONTH, PLAYERS_RETAINED_MONTH_PERC,
|
||||
//
|
||||
// TPS_SPIKE_MONTH, TPS_SPIKE_WEEK, TPS_SPIKE_DAY,
|
||||
// AVG_TPS_MONTH, AVG_TPS_WEEK, AVG_TPS_DAY,
|
||||
|
@ -6,12 +6,12 @@ package com.djrapitops.plan.system.webserver.pages.parsing;
|
||||
|
||||
import com.djrapitops.plan.api.exceptions.ParseException;
|
||||
import com.djrapitops.plan.data.Actions;
|
||||
import com.djrapitops.plan.data.calculation.ActivityIndex;
|
||||
import com.djrapitops.plan.data.container.Action;
|
||||
import com.djrapitops.plan.data.container.Session;
|
||||
import com.djrapitops.plan.data.store.containers.PerServerContainer;
|
||||
import com.djrapitops.plan.data.store.containers.PlayerContainer;
|
||||
import com.djrapitops.plan.data.store.keys.PlayerKeys;
|
||||
import com.djrapitops.plan.data.store.mutators.ActivityIndex;
|
||||
import com.djrapitops.plan.data.store.mutators.PerServerDataMutator;
|
||||
import com.djrapitops.plan.data.store.mutators.SessionsMutator;
|
||||
import com.djrapitops.plan.data.store.mutators.formatting.Formatter;
|
||||
|
@ -1,10 +1,10 @@
|
||||
package com.djrapitops.plan.utilities.analysis;
|
||||
|
||||
import com.djrapitops.plan.data.PlayerProfile;
|
||||
import com.djrapitops.plan.data.calculation.ActivityIndex;
|
||||
import com.djrapitops.plan.data.container.Session;
|
||||
import com.djrapitops.plan.data.container.StickyData;
|
||||
import com.djrapitops.plan.data.store.keys.SessionKeys;
|
||||
import com.djrapitops.plan.data.store.mutators.ActivityIndex;
|
||||
import com.djrapitops.plan.data.store.mutators.RetentionData;
|
||||
import com.djrapitops.plan.data.time.GMTimes;
|
||||
import com.djrapitops.plan.data.time.WorldTimes;
|
||||
import com.djrapitops.plan.system.settings.WorldAliasSettings;
|
||||
@ -243,24 +243,21 @@ public class AnalysisUtils {
|
||||
return gmTimesPerAlias;
|
||||
}
|
||||
|
||||
public static StickyData average(Collection<StickyData> stuck) {
|
||||
public static RetentionData average(Collection<RetentionData> stuck) {
|
||||
int size = stuck.size();
|
||||
|
||||
double totalIndex = 0.0;
|
||||
double totalMsgsSent = 0.0;
|
||||
double totalPlayersOnline = 0.0;
|
||||
|
||||
for (StickyData stickyData : stuck) {
|
||||
totalIndex += stickyData.getActivityIndex();
|
||||
totalMsgsSent += stickyData.getMessagesSent();
|
||||
totalPlayersOnline += stickyData.getOnlineOnJoin();
|
||||
for (RetentionData retentionData : stuck) {
|
||||
totalIndex += retentionData.getActivityIndex();
|
||||
totalPlayersOnline += retentionData.getOnlineOnJoin();
|
||||
}
|
||||
|
||||
double averageIndex = totalIndex / (double) size;
|
||||
double averageMessagesSent = totalMsgsSent / (double) size;
|
||||
double averagePlayersOnline = totalPlayersOnline / (double) size;
|
||||
|
||||
return new StickyData(averageIndex, averageMessagesSent, averagePlayersOnline);
|
||||
return new RetentionData(averageIndex, averagePlayersOnline);
|
||||
}
|
||||
|
||||
public static String getLongestWorldPlayed(Session session) {
|
||||
|
@ -4,7 +4,7 @@
|
||||
*/
|
||||
package com.djrapitops.plan.utilities.html.graphs;
|
||||
|
||||
import com.djrapitops.plan.data.calculation.ActivityIndex;
|
||||
import com.djrapitops.plan.data.store.mutators.ActivityIndex;
|
||||
import com.djrapitops.plan.data.store.mutators.PlayersMutator;
|
||||
import com.djrapitops.plan.system.settings.theme.Theme;
|
||||
import com.djrapitops.plan.system.settings.theme.ThemeVal;
|
||||
|
@ -4,7 +4,7 @@
|
||||
*/
|
||||
package com.djrapitops.plan.utilities.html.graphs.pie;
|
||||
|
||||
import com.djrapitops.plan.data.calculation.ActivityIndex;
|
||||
import com.djrapitops.plan.data.store.mutators.ActivityIndex;
|
||||
import com.djrapitops.plan.system.settings.theme.Theme;
|
||||
import com.djrapitops.plan.system.settings.theme.ThemeVal;
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
package com.djrapitops.plan.utilities.html.tables;
|
||||
|
||||
import com.djrapitops.plan.api.PlanAPI;
|
||||
import com.djrapitops.plan.data.calculation.ActivityIndex;
|
||||
import com.djrapitops.plan.data.container.GeoInfo;
|
||||
import com.djrapitops.plan.data.element.TableContainer;
|
||||
import com.djrapitops.plan.data.store.containers.PlayerContainer;
|
||||
import com.djrapitops.plan.data.store.keys.PlayerKeys;
|
||||
import com.djrapitops.plan.data.store.mutators.ActivityIndex;
|
||||
import com.djrapitops.plan.data.store.mutators.GeoInfoMutator;
|
||||
import com.djrapitops.plan.data.store.mutators.SessionsMutator;
|
||||
import com.djrapitops.plan.data.store.mutators.formatting.Formatters;
|
||||
|
@ -2,7 +2,7 @@ package com.djrapitops.plan.utilities.html.tables;
|
||||
|
||||
import com.djrapitops.plan.api.PlanAPI;
|
||||
import com.djrapitops.plan.data.PlayerProfile;
|
||||
import com.djrapitops.plan.data.calculation.ActivityIndex;
|
||||
import com.djrapitops.plan.data.store.mutators.ActivityIndex;
|
||||
import com.djrapitops.plan.data.store.mutators.formatting.Formatters;
|
||||
import com.djrapitops.plan.system.info.server.ServerInfo;
|
||||
import com.djrapitops.plan.system.settings.Settings;
|
||||
|
@ -3,6 +3,7 @@ package com.djrapitops.plan.data.calculation;
|
||||
import com.djrapitops.plan.data.container.Session;
|
||||
import com.djrapitops.plan.data.store.containers.PlayerContainer;
|
||||
import com.djrapitops.plan.data.store.keys.PlayerKeys;
|
||||
import com.djrapitops.plan.data.store.mutators.ActivityIndex;
|
||||
import com.djrapitops.plan.system.settings.Settings;
|
||||
import com.djrapitops.plugin.api.TimeAmount;
|
||||
import org.junit.BeforeClass;
|
||||
|
@ -4,8 +4,8 @@
|
||||
*/
|
||||
package com.djrapitops.plan.utilities.html.graphs;
|
||||
|
||||
import com.djrapitops.plan.data.calculation.ActivityIndex;
|
||||
import com.djrapitops.plan.data.container.TPS;
|
||||
import com.djrapitops.plan.data.store.mutators.ActivityIndex;
|
||||
import com.djrapitops.plan.system.settings.Settings;
|
||||
import com.djrapitops.plan.utilities.html.graphs.line.*;
|
||||
import com.djrapitops.plan.utilities.html.graphs.stack.AbstractStackGraph;
|
||||
|
Loading…
Reference in New Issue
Block a user