Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java: Sum two or more time series

Tags:

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)

like image 280
Eduardo Avatar asked Mar 06 '18 12:03

Eduardo


1 Answers

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;
}
like image 115
daniu Avatar answered Nov 15 '22 06:11

daniu