mirror of
https://github.com/plan-player-analytics/Plan.git
synced 2024-09-07 04:18:26 +02:00
24h new player retention prediction
This commit is contained in:
parent
63c2db3cb2
commit
4d48954473
@ -136,6 +136,12 @@ public class ActivityIndex {
|
||||
return formatter.apply(value);
|
||||
}
|
||||
|
||||
public double distance(ActivityIndex other) {
|
||||
// Logarithm makes the distance function more skewed towards active players
|
||||
// https://www.wolframalpha.com/input/?i=plot+y+%3D+log(5+-+5+*+(1+%2F+(pi%2F2+*+x%2B1)))+and+5+-+5+*+(1+%2F+(pi%2F2+*+x%2B1))+and+y+%3D1+and+y+%3D+2+and+y+%3D+3+and+y+%3D+3.75+from+-0.5+to+3
|
||||
return Math.abs(Math.log(other.value) - Math.log(value));
|
||||
}
|
||||
|
||||
public String getGroup() {
|
||||
if (value >= VERY_ACTIVE) {
|
||||
return "Very Active";
|
||||
|
@ -75,6 +75,18 @@ public class RetentionData {
|
||||
.orElse(0);
|
||||
}
|
||||
|
||||
public static int countRetentionPrediction(
|
||||
Collection<ActivityIndex> newPlayers,
|
||||
ActivityIndex retained,
|
||||
ActivityIndex nonRetained
|
||||
) {
|
||||
int count = 0;
|
||||
for (ActivityIndex player : newPlayers) {
|
||||
if (player.distance(retained) < player.distance(nonRetained)) count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public double distance(RetentionData data) {
|
||||
double num = 0;
|
||||
num += Math.abs(data.activityIndex - activityIndex) * 2.0;
|
||||
|
@ -25,9 +25,7 @@ import com.djrapitops.plan.db.sql.tables.UserInfoTable;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.djrapitops.plan.db.sql.parsing.Sql.*;
|
||||
@ -214,4 +212,116 @@ public class ActivityIndexQueries {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Query<Collection<ActivityIndex>> activityIndexForNewPlayers(long after, long before, UUID serverUUID, Long threshold) {
|
||||
String selectNewUUIDs = SELECT + UserInfoTable.USER_UUID +
|
||||
FROM + UserInfoTable.TABLE_NAME +
|
||||
WHERE + UserInfoTable.REGISTERED + "<=?" +
|
||||
AND + UserInfoTable.REGISTERED + ">=?" +
|
||||
AND + UserInfoTable.SERVER_UUID + "=?";
|
||||
|
||||
String sql = SELECT + "activity_index" +
|
||||
FROM + '(' + selectNewUUIDs + ") n" +
|
||||
INNER_JOIN + '(' + selectActivityIndexSQL() + ") a on n." + SessionsTable.USER_UUID + "=a." + SessionsTable.USER_UUID;
|
||||
|
||||
return new QueryStatement<Collection<ActivityIndex>>(sql) {
|
||||
@Override
|
||||
public void prepare(PreparedStatement statement) throws SQLException {
|
||||
statement.setLong(1, before);
|
||||
statement.setLong(2, after);
|
||||
statement.setString(3, serverUUID.toString());
|
||||
setSelectActivityIndexSQLParameters(statement, 4, threshold, serverUUID, before);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ActivityIndex> processResults(ResultSet set) throws SQLException {
|
||||
Collection<ActivityIndex> indexes = new ArrayList<>();
|
||||
while (set.next()) {
|
||||
indexes.add(new ActivityIndex(set.getDouble("activity_index"), before));
|
||||
}
|
||||
return indexes;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Query<ActivityIndex> averageActivityIndexForRetainedPlayers(long after, long before, UUID serverUUID, Long threshold) {
|
||||
String selectNewUUIDs = SELECT + UserInfoTable.USER_UUID +
|
||||
FROM + UserInfoTable.TABLE_NAME +
|
||||
WHERE + UserInfoTable.REGISTERED + "<=?" +
|
||||
AND + UserInfoTable.REGISTERED + ">=?" +
|
||||
AND + UserInfoTable.SERVER_UUID + "=?";
|
||||
|
||||
String selectUniqueUUIDs = SELECT + "DISTINCT " + SessionsTable.USER_UUID +
|
||||
FROM + SessionsTable.TABLE_NAME +
|
||||
WHERE + SessionsTable.SESSION_START + ">=?" +
|
||||
AND + SessionsTable.SESSION_END + "<=?" +
|
||||
AND + SessionsTable.SERVER_UUID + "=?";
|
||||
|
||||
String sql = SELECT + "AVG(activity_index) as average" +
|
||||
FROM + '(' + selectNewUUIDs + ") n" +
|
||||
INNER_JOIN + '(' + selectUniqueUUIDs + ") u on n." + SessionsTable.USER_UUID + "=u." + SessionsTable.USER_UUID +
|
||||
INNER_JOIN + '(' + selectActivityIndexSQL() + ") a on n." + SessionsTable.USER_UUID + "=a." + SessionsTable.USER_UUID;
|
||||
|
||||
return new QueryStatement<ActivityIndex>(sql) {
|
||||
@Override
|
||||
public void prepare(PreparedStatement statement) throws SQLException {
|
||||
statement.setLong(1, before);
|
||||
statement.setLong(2, after);
|
||||
statement.setString(3, serverUUID.toString());
|
||||
|
||||
// Have played in the last half of the time frame
|
||||
long half = before - (after - before) / 2;
|
||||
statement.setLong(4, half);
|
||||
statement.setLong(5, before);
|
||||
statement.setString(6, serverUUID.toString());
|
||||
setSelectActivityIndexSQLParameters(statement, 7, threshold, serverUUID, before);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActivityIndex processResults(ResultSet set) throws SQLException {
|
||||
return set.next() ? new ActivityIndex(set.getDouble("average"), before) : new ActivityIndex(0.0, before);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Query<ActivityIndex> averageActivityIndexForNonRetainedPlayers(long after, long before, UUID serverUUID, Long threshold) {
|
||||
String selectNewUUIDs = SELECT + UserInfoTable.USER_UUID +
|
||||
FROM + UserInfoTable.TABLE_NAME +
|
||||
WHERE + UserInfoTable.REGISTERED + "<=?" +
|
||||
AND + UserInfoTable.REGISTERED + ">=?" +
|
||||
AND + UserInfoTable.SERVER_UUID + "=?";
|
||||
|
||||
String selectUniqueUUIDs = SELECT + "DISTINCT " + SessionsTable.USER_UUID +
|
||||
FROM + SessionsTable.TABLE_NAME +
|
||||
WHERE + SessionsTable.SESSION_START + ">=?" +
|
||||
AND + SessionsTable.SESSION_END + "<=?" +
|
||||
AND + SessionsTable.SERVER_UUID + "=?";
|
||||
|
||||
String sql = SELECT + "AVG(activity_index) as average" +
|
||||
FROM + '(' + selectNewUUIDs + ") n" +
|
||||
LEFT_JOIN + '(' + selectUniqueUUIDs + ") u on n." + SessionsTable.USER_UUID + "=u." + SessionsTable.USER_UUID +
|
||||
INNER_JOIN + '(' + selectActivityIndexSQL() + ") a on n." + SessionsTable.USER_UUID + "=a." + SessionsTable.USER_UUID +
|
||||
WHERE + "n." + SessionsTable.USER_UUID + IS_NULL;
|
||||
|
||||
return new QueryStatement<ActivityIndex>(sql) {
|
||||
@Override
|
||||
public void prepare(PreparedStatement statement) throws SQLException {
|
||||
statement.setLong(1, before);
|
||||
statement.setLong(2, after);
|
||||
statement.setString(3, serverUUID.toString());
|
||||
|
||||
// Have played in the last half of the time frame
|
||||
long half = before - (after - before) / 2;
|
||||
statement.setLong(4, half);
|
||||
statement.setLong(5, before);
|
||||
statement.setString(6, serverUUID.toString());
|
||||
setSelectActivityIndexSQLParameters(statement, 7, threshold, serverUUID, before);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActivityIndex processResults(ResultSet set) throws SQLException {
|
||||
return set.next() ? new ActivityIndex(set.getDouble("average"), before) : new ActivityIndex(0.0, before);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -19,9 +19,11 @@ package com.djrapitops.plan.system.json;
|
||||
import com.djrapitops.plan.data.container.TPS;
|
||||
import com.djrapitops.plan.data.store.keys.SessionKeys;
|
||||
import com.djrapitops.plan.data.store.mutators.PlayersOnlineResolver;
|
||||
import com.djrapitops.plan.data.store.mutators.RetentionData;
|
||||
import com.djrapitops.plan.data.store.mutators.SessionsMutator;
|
||||
import com.djrapitops.plan.data.store.mutators.TPSMutator;
|
||||
import com.djrapitops.plan.db.Database;
|
||||
import com.djrapitops.plan.db.access.queries.analysis.ActivityIndexQueries;
|
||||
import com.djrapitops.plan.db.access.queries.analysis.PlayerCountQueries;
|
||||
import com.djrapitops.plan.db.access.queries.objects.SessionQueries;
|
||||
import com.djrapitops.plan.db.access.queries.objects.TPSQueries;
|
||||
@ -29,6 +31,7 @@ import com.djrapitops.plan.db.access.queries.objects.UserInfoQueries;
|
||||
import com.djrapitops.plan.system.database.DBSystem;
|
||||
import com.djrapitops.plan.system.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.system.settings.paths.DisplaySettings;
|
||||
import com.djrapitops.plan.system.settings.paths.TimeSettings;
|
||||
import com.djrapitops.plan.utilities.formatting.Formatter;
|
||||
import com.djrapitops.plan.utilities.formatting.Formatters;
|
||||
|
||||
@ -83,6 +86,7 @@ public class OnlineActivityOverviewJSONParser implements TabJSONParser<Map<Strin
|
||||
long halfMonthAgo = now - TimeUnit.DAYS.toMillis(15L);
|
||||
long monthAgo = now - TimeUnit.DAYS.toMillis(30L);
|
||||
int timeZoneOffset = timeZone.getOffset(now);
|
||||
Long playThreshold = config.get(TimeSettings.ACTIVE_PLAY_THRESHOLD);
|
||||
|
||||
Map<String, Object> numbers = new HashMap<>();
|
||||
|
||||
@ -106,6 +110,7 @@ public class OnlineActivityOverviewJSONParser implements TabJSONParser<Map<Strin
|
||||
|
||||
Integer new30d = db.query(PlayerCountQueries.newPlayerCount(monthAgo, now, serverUUID));
|
||||
Integer new7d = db.query(PlayerCountQueries.newPlayerCount(weekAgo, now, serverUUID));
|
||||
Integer new1d = db.query(PlayerCountQueries.newPlayerCount(dayAgo, now, serverUUID));
|
||||
numbers.put("new_players_30d", new30d);
|
||||
numbers.put("new_players_30d_trend", new Trend(
|
||||
db.query(PlayerCountQueries.newPlayerCount(monthAgo, halfMonthAgo, serverUUID)),
|
||||
@ -113,7 +118,7 @@ public class OnlineActivityOverviewJSONParser implements TabJSONParser<Map<Strin
|
||||
false
|
||||
));
|
||||
numbers.put("new_players_7d", new7d);
|
||||
numbers.put("new_players_24h", db.query(PlayerCountQueries.newPlayerCount(dayAgo, now, serverUUID)));
|
||||
numbers.put("new_players_24h", new1d);
|
||||
|
||||
numbers.put("new_players_30d_avg", db.query(PlayerCountQueries.averageNewPlayerCount(monthAgo, now, timeZoneOffset, serverUUID)));
|
||||
numbers.put("new_players_30d_avg_trend", new Trend(
|
||||
@ -132,8 +137,15 @@ public class OnlineActivityOverviewJSONParser implements TabJSONParser<Map<Strin
|
||||
numbers.put("new_players_retention_30d_perc", percentageFormatter.apply(retentionPerc30d));
|
||||
numbers.put("new_players_retention_7d", retained7d);
|
||||
numbers.put("new_players_retention_7d_perc", percentageFormatter.apply(retentionPerc7d));
|
||||
numbers.put("new_players_retention_24h", 0); // TODO
|
||||
numbers.put("new_players_retention_24h_perc", percentageFormatter.apply(-1.0)); // TODO
|
||||
|
||||
int prediction1d = RetentionData.countRetentionPrediction(
|
||||
db.query(ActivityIndexQueries.activityIndexForNewPlayers(dayAgo, now, serverUUID, playThreshold)),
|
||||
db.query(ActivityIndexQueries.averageActivityIndexForRetainedPlayers(monthAgo, now, serverUUID, playThreshold)),
|
||||
db.query(ActivityIndexQueries.averageActivityIndexForNonRetainedPlayers(monthAgo, now, serverUUID, playThreshold))
|
||||
);
|
||||
double retentionPerc1d = new1d != 0 ? (double) prediction1d / new1d : -1;
|
||||
numbers.put("new_players_retention_24h", prediction1d);
|
||||
numbers.put("new_players_retention_24h_perc", percentageFormatter.apply(retentionPerc1d));
|
||||
|
||||
Long playtimeMonth = db.query(SessionQueries.playtime(monthAgo, now, serverUUID));
|
||||
Long playtimeWeek = db.query(SessionQueries.playtime(weekAgo, now, serverUUID));
|
||||
|
@ -919,7 +919,7 @@
|
||||
class="fas fa-fw fa-globe col-green"></i>
|
||||
Geolocations</h6>
|
||||
</div>
|
||||
<div class="chart-area row">
|
||||
<div class="chart-area row" style="height: 100%;">
|
||||
<div class="col-xs-12 col-sm-12 col-md-3 col-lg-3">
|
||||
<div id="countryBarChart"><span class="loader"></span></div>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user