blob: a49f13bd9772a166fbb27971ebdd02cdd3d2c2c9 [file] [log] [blame]
//
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package com.google.privacy.differentialprivacy.example;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.privacy.differentialprivacy.example.RestaurantSchedule.CLOSING_HOUR;
import static com.google.privacy.differentialprivacy.example.RestaurantSchedule.OPENING_HOUR;
import static com.google.privacy.differentialprivacy.example.RestaurantSchedule.VALID_HOURS;
import static java.util.stream.Collectors.toMap;
import com.google.common.collect.ImmutableSortedMap;
import com.google.privacy.differentialprivacy.Count;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
/**
* Loads {@link InputFilePath#DAY_STATISTICS} file, calculates non-anonymized and
* anonymized counts of visitors entering a restaurant every hour and prints result
* to {@link #NON_PRIVATE_OUTPUT} and {@link #PRIVATE_OUTPUT}.
* Uses {@link Count}.
*/
public class CountVisitsPerHour {
private static final String NON_PRIVATE_OUTPUT = "non_private_counts_per_hour.csv";
private static final String PRIVATE_OUTPUT = "private_counts_per_hour.csv";
private static final double LN_3 = Math.log(3);
private CountVisitsPerHour() {}
public static void run() {
Collection<Visit> dailyVisits = IOUtils.readDailyVisits(InputFilePath.DAY_STATISTICS);
Map<Integer, Integer> nonPrivateCounts = getNonPrivateCounts(dailyVisits);
Map<Integer, Integer> privateCounts = getPrivateCounts(dailyVisits);
IOUtils.writeCountsPerHourOfDay(nonPrivateCounts, NON_PRIVATE_OUTPUT);
IOUtils.writeCountsPerHourOfDay(privateCounts, PRIVATE_OUTPUT);
}
/**
* Calculates raw count of {@link Visit}s per hour of day.
*
* <p>{@return} a {@link ImmutableSortedMap} that maps an hour to a raw count of visits for the
* hours between {@link RestaurantSchedule#OPENING_HOUR} and {@link
* RestaurantSchedule#CLOSING_HOUR}.
*/
private static ImmutableSortedMap<Integer, Integer> getNonPrivateCounts(
Collection<Visit> visits) {
Map<Integer, Integer> counts = new TreeMap<>();
for (int h = OPENING_HOUR; h <= CLOSING_HOUR; h++) {
counts.put(h, 0);
}
visits.forEach(v -> {
int hour = v.entryTime().getHour();
// Validate that the visit happened during one of valid hours.
checkArgument(VALID_HOURS.contains(hour));
int newCount = counts.get(hour) + 1;
counts.put(hour, newCount);
});
return ImmutableSortedMap.copyOf(counts);
}
/**
* Calculates anonymized ("private") count of {@link Visit}s per hour of day.
*
* {@return} a {@link ImmutableSortedMap} that maps an hour to anonymized count of visits.
*/
private static ImmutableSortedMap<Integer, Integer> getPrivateCounts(Collection<Visit> visits) {
// Construct DP Count objects which will be used to calculate DP counts
// one Count is created for every work hour.
Map<Integer, Count> dpCounts = new HashMap<>();
for (int i = OPENING_HOUR; i <= CLOSING_HOUR; i++) {
Count dpCount = Count.builder()
.epsilon(LN_3)
.maxPartitionsContributed(1)
.build();
dpCounts.put(i, dpCount);
}
// Go through all visits and update Counts at the corresponding hours.
visits.forEach(v -> dpCounts.get(v.entryTime().getHour()).increment());
// Trigger DP logic to produce anonymized counts of visits.
return ImmutableSortedMap.copyOf(
dpCounts.entrySet().stream()
.collect(
toMap(Map.Entry::getKey, e -> (int) e.getValue().computeResult())));
}
}