I have multiple time series:
x
| date | value |
| 2017-01-01 | 1 |
| 2017-01-05 | 4 |
| ... | ... |
y
| date | value |
| 2017-01-03 | 3 |
| 2017-01-04 | 2 |
| ... | ... |
Frustratingly in my dataset there isn't always a matching date in both series. For scenarios where there is one missing I want to use the last available date (or 0 if there isnt one).
e.g for 2017-01-03
I would use y=3
and x=1
(from the date before) to get output = 3 + 1 = 4
I have each timeseries in the form:
class Timeseries {
List<Event> x = ...;
}
class Event {
LocalDate date;
Double value;
}
and have read them into a List<Timeseries> allSeries
I thought I might be able to sum them using streams
List<TimeSeries> allSeries = ...
Map<LocalDate, Double> byDate = allSeries.stream()
.flatMap(s -> s.getEvents().stream())
.collect(Collectors.groupingBy(Event::getDate,Collectors.summingDouble(Event::getValue)));
But this wouldnt have my missing date logic I mentioned above.
How else could I achieve this? (It doesnt have to be by streams)
I'd say you need to expand the Timeseries class for the appropriate query function.
class Timeseries {
private SortedMap<LocalDate, Integer> eventValues = new TreeMap<>();
private List<Event> eventList;
public Timeseries(List<Event> events) {
events.forEach(e -> eventValue.put(e.getDate(), e.getValue());
eventList=new ArrayList(events);
}
public List<Event> getEvents() {
return Collections.unmodifiableList(eventList);
}
public Integer getValueByDate(LocalDate date) {
Integer value = eventValues.get(date);
if (value == null) {
// get values before the requested date
SortedMap<LocalDate, Integer> head = eventValues.headMap(date);
value = head.isEmpty()
? 0 // none before
: head.get(head.lastKey()); // first before
}
return value;
}
}
Then to merge
Map<LocalDate, Integer> values = new TreeMap<>();
List<LocalDate> allDates = allSeries.stream().flatMap(s -> s.getEvents().getDate())
.distinct().collect(toList());
for (LocalDate date : allDates) {
for (Timeseries series : allSeries) {
values.merge(date, series.getValueByDate(date), Integer::ad);
}
}
Edit: actually, the NavigableMap
interface is even more useful in this case, it makes the missing data case
Integer value = eventValues.get(date);
if (value == null) {
Entry<LocalDate, Integer> ceiling = eventValues.ceilingKey(date);
value = ceiling != null ? eventValues.get(ceiling) : 0;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With