`Series`

data, it is often useful to index each element of the series with multiple labels and then select and aggregrate data based on these indices. For example, for a collection of time series data, each point in time might be identified by trial number, block number, one or more experimental conditions, etc. This tutorial shows how to create and leverage such "multi-indices" when working with Series objects.

Let's start by building a simple Series with only a single record.

```
In [1]:
```from thunder import Series
from numpy import arange, array

```
In [2]:
```data = tsc.loadSeriesFromArray(arange(12))
data.first()

```
Out[2]:
```

By default, the index on the series will label the elemets with ascending integers.

```
In [3]:
```data.index

```
Out[3]:
```

```
In [4]:
```trial = array([1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2])

```
In [5]:
```block = array([1, 1, 2, 2, 3, 3, 1, 1, 2, 2, 3, 3])

Finally, in this simple example, we have two time points within each block.

```
In [6]:
```point = array([1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2])

```
In [7]:
```index = array([trial, block, point]).T
data.index = index

To inspect the index, we look at the transpose so it lines up with the Series.

```
In [8]:
```data.index.T

```
Out[8]:
```

```
In [9]:
```selected = data.selectByIndex(1, level=0)
def displaySeries(series):
print "index"
print "-----"
print series.index.T
print "series"
print "------"
print series.values().first()
displaySeries(selected)

```
```

```
In [10]:
```selected = data.selectByIndex(1, level=0, squeeze=True)
displaySeries(selected)

```
```

```
In [11]:
```selected = data.selectByIndex([2, 3], level=1)
displaySeries(selected)

```
```

```
In [12]:
```selected = data.selectByIndex([1, [2, 3]], level=[0, 1])
displaySeries(selected)

```
```

*except* those that match the values). This is accomplished with the "filter" keyword. To demonstrate, lets get all of the blocks *except* for the 2nd (level = 1; value = 2).

```
In [13]:
```selected = data.selectByIndex(2, level=1, filter=True)
displaySeries(selected)

```
```

The second major multi-index operation is aggregation. Aggregation can be thought of as a two step-process. First a level is selected and the series is partitioned into pieces that share the index value at that level. Second an aggregating function is applied to each of these partitions, and a new series is reconsituted with one element for the aggregate value computed on each piece. The aggregating function should take an array as input and return a single numeric values as output.

As a simple initial demonstration, let's find the average value of our series for each trial (level = 0).

```
In [14]:
```from numpy import mean
aggregated = data.seriesAggregateByIndex(mean, level=0)
displaySeries(aggregated)

```
```

The same operation can be called through the convienience function `seriesMeanByIndex`

```
In [15]:
```aggregated = data.seriesMeanByIndex(level=0)
displaySeries(aggregated)

```
```

```
In [16]:
```aggregated = data.seriesMaxByIndex(level=[0, 2])
displaySeries(aggregated)

```
```