package edu.hawaii.jmotif.timeseries;
import java.io.Serializable;
import java.util.Iterator;
import java.util.Vector;
import org.hackystat.utilities.stacktrace.StackTrace;
/**
* Implements time-series. It's important to know how it handles NaN values. If input data has NaN's
* or INFINITY's as entries it will be kept same, but if you specify particular numeric value as
* NaN, (say a 0 for missing values) it'll be converted into Double.NaN internally and you have to
* convert it back to your number later.
*
* @author Pavel Senin.
*
*/
public final class Timeseries implements Iterable<TPoint>, Cloneable, Serializable {
private static final String COMMA = ", ";
private final Vector<TPoint> series = new Vector<TPoint>();
private static final long serialVersionUID = 7526471155622776148L;
/**
* Constructor.
*
*/
public Timeseries() {
assert true;
}
/**
* Constructor.
*
* @param values the timeseries values.
* @param tstamps the timestamps.
* @throws TSException if error occurs.
*/
public Timeseries(double[] values, long[] tstamps) throws TSException {
if (values.length == tstamps.length) {
for (int i = 0; i < values.length; i++) {
this.series.add(new TPoint(values[i], tstamps[i]));
}
}
else {
throw new TSException("The lengths of the values and timestamps arrays are not equal!");
}
}
/**
* Constructor.
*
* @param values the timeseries values.
* @param tstamps the timestamps.
* @param nanValue the Not a Number value for this timeseries.
* @throws TSException if error occurs.
*/
public Timeseries(double[] values, long[] tstamps, double nanValue) throws TSException {
if (values.length == tstamps.length) {
for (int i = 0; i < values.length; i++) {
if (nanValue == values[i]) {
this.series.add(new TPoint(Double.NaN, tstamps[i]));
}
else {
this.series.add(new TPoint(values[i], tstamps[i]));
}
}
}
else {
throw new TSException("The lengths of the values and timestamps arrays are not equal!");
}
}
/**
* Add the point at the end of the timeseries. No consistency check is conducted.
*
* @param p The point to add.
*/
public void add(TPoint p) {
this.series.add(p);
}
/**
* Add the point to the timeseries, inserting it at the position specified by the timestamp.
*
* @param p The point to add.
*/
public void addByTime(TPoint p) {
if (this.series.isEmpty()) {
this.series.add(p);
return;
}
else if (p.tstamp() > this.series.lastElement().tstamp()) {
this.series.add(p);
return;
}
else if (p.tstamp() < this.series.firstElement().tstamp()) {
this.series.insertElementAt(p, 0);
return;
}
int pos2insert = findPositions(p);
this.series.insertElementAt(p, pos2insert);
}
/**
* Finds the position for the new point insertion.
*
* @param p The point to add to the timeseries.
* @return position found.
*/
private int findPositions(TPoint p) {
int i = 0;
int j = this.series.size();
int k = 0;
while ((j - i) > 1) {
k = i + (j - i) / 2;
if (p.tstamp() == this.series.elementAt(k).tstamp()) {
return k;
}
else if (p.tstamp() < this.series.elementAt(k).tstamp()) {
j = k;
}
else {
i = k;
}
}
if ((j - i) == 1) {
return j;
}
return -1;
}
/**
* Remove an element from the timeseries.
*
* @param pos The position of the element to remove.
* @throws TSException if wrong position specified.
*/
public void removeAt(int pos) throws TSException {
if (pos >= 0 && pos < this.series.size()) {
this.series.remove(pos);
}
else {
throw new TSException("Illegal position specified for removal: " + pos + ", series length: "
+ this.size());
}
}
/**
* Get the length of the timeseries.
*
* @return the length of the timeseries.
*/
public int size() {
return this.series.size();
}
/**
* Return the element at the position.
*
* @param i the position of the element.
* @return the element at the position i.
*/
public TPoint elementAt(int i) {
return this.series.get(i);
}
/**
* Return the array of values.
*
* @return the array of values.
*/
public double[] values() {
double[] res = new double[this.series.size()];
for (int i = 0; i < this.series.size(); i++) {
res[i] = this.series.get(i).value();
}
return res;
}
/**
* Return the one-row matrix of values.
*
* @return the one-row matrix of values.
*/
public double[][] valuesAsMatrix() {
double[][] res = new double[1][this.series.size()];
for (int i = 0; i < this.series.size(); i++) {
res[0][i] = this.series.get(i).value();
}
return res;
}
/**
* Return the array of timestamps.
*
* @return the array of timestamps.
*/
public long[] tstamps() {
long[] res = new long[this.series.size()];
for (int i = 0; i < this.series.size(); i++) {
res[i] = this.series.get(i).tstamp();
}
return res;
}
/**
* Extract interval from timeseries. It is inclusive, i.e. subsection(0,5) will include 6 elements
* from ts[0] to ts[5].
*
* @param start the interval start.
* @param end the interval end.
* @return the timeseries interval.
* @throws TSException if error occurs.
*/
public Timeseries subsection(int start, int end) throws TSException {
if (start >= 0 && end < this.series.size()) {
int len = end - start + 1;
double[] val = new double[len];
long[] ts = new long[len];
for (int i = start; i <= end; i++) {
TPoint tp = this.series.get(i);
val[i - start] = tp.value();
ts[i - start] = tp.tstamp();
}
return new Timeseries(val, ts);
}
else {
throw new TSException("Invalid interval specified: timeseries size " + this.series.size()
+ " interval [" + start + ", " + end + "]");
}
}
/**
* {@inheritDoc}
*/
public int hashCode() {
int hash = 7;
if (this.series.size() > 0) {
hash = hash + 31 * this.series.get(0).hashCode();
}
for (int i = 1; i < this.series.size(); i++) {
hash = hash + this.series.get(i).hashCode();
}
return hash;
}
/**
* {@inheritDoc}
*/
public boolean equals(Object o) {
if (o instanceof Timeseries) {
Timeseries ot = (Timeseries) o;
if (this.size() == ot.size()) {
for (int i = 0; i < this.series.size(); i++) {
if (this.series.get(i).equals(ot.elementAt(i))) {
continue;
}
else {
return false;
}
}
return true;
}
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
public Iterator<TPoint> iterator() {
return this.series.iterator();
}
/**
* {@inheritDoc}
*/
public Timeseries clone() throws CloneNotSupportedException {
int len = this.series.size();
double[] val = new double[len];
long[] ts = new long[len];
for (int i = 0; i < len; i++) {
TPoint tp = this.series.get(i);
val[i] = tp.value();
ts[i] = tp.tstamp();
}
try {
return new Timeseries(val, ts);
}
catch (TSException e) {
throw new CloneNotSupportedException("Exception thrown! " + StackTrace.toString(e));
}
}
/**
* {@inheritDoc}
*/
public String toString() {
StringBuffer res = new StringBuffer(500);
for (TPoint tp : this.series) {
res.append(tp.value() + COMMA);
}
return res.substring(0, res.length() - 2);
}
}