PowerPlotPercolation logo

Data handling

These are examples of common manipulations of WSData objects, the underlying data model of PowerPlot, are shown. Some operations are only supported on iOS4.0 and later.

This page describes the class “DataHandling” in the PowerPlot demo app.

Data handling methods

Structure of data model

The data model is represented by instances of class WSData. WSData is a container class of instances of WSDatum. Each instance of the latter represents a single “data point”. Such a data point must have a value and can have one or more of the following: valueX, annotation, errorMinX, errorMaxX, errorMinY, errorMaxY, and errorCorr.

Additionally, each instance of WSDatum can also hold a customDatum which needs to implement the NSCoding, NSCopying and NSObject protocols and a delegate which needs to implement NSObject only. When WSDatum is serialized or copied the customDatum will also be serialized or copied, but the delegate is set to nil, instead.

Internally, each WSDatum stores its information (excluding customDatum and delegate which are handled separately) in a NSMutableDictionary, so it is possible to extend this class with categories that store additional information without having to subclass it.

Note that also WSData can be serialized and copied and that it can be enumerated and iterated over like any regular array. Creation of WSData objects can be done by adding WSDatum instances to it manually. Alternatively, one can pass arrays of objects as it is done frequently in the DemoData class in the example app, e.g.:

+ (WSData *)totalProfitBad {
    float cashFlow[12] = { 0, -10, -17, -35, -6, 5,
                           2, 15, 29, 38, 51, 45 };
    WSData *result = [WSData dataWithValues:[WSData arrayWithFloat:cashFlow
                                                               len:12]];
    
    return [result indexedData];    
}

The above operations can be reversed using the extraction methods floatsFromDataX and floatsFromDataY with PowerPlot v1.2.2 and above (requires iOS 4.0 or later).

The advantage of putting data into the additional WSData wrapper is the flexibility: Several data-related properties – like e.g. bar chart properties – can be associated with individual datum objects. In fact, these things are all stored in the customDatum property of WSDatum and thus will be preserved when the data is written to disk and/or copied! There is no need to modify the implementation of WSDatum or WSData when new data properties are introduced with new chart types in the future. Furthermore, the powerful data handling features discussed below were introduced without needing to change the implementations of these classes!

Data binning

Data binning is a frequent operation in business intelligence analysis. Thus, this operation is built-in into the class WSData. The binning is best illustrated with the following example code:

    WSData *electionD = [DemoData electionResults2009];
    WSData *barData = [electionD indexedData];
    WSData *binned = [WSBinning binWithData:barData
                                  binNumber:5
                                   selector:@selector(value)
                                      range:NARangeMake(0.0, 50.0)];

Using the given instance barData the new data set binned is created by counting the number of values (as selected by @selector(value) on the WSDatum objects) of the input data. The bins are defined using binNumber and withRange, i.e., five bins ranging from 0 to 50 are created, each with a size of 10.

Map data

The map-command is a basic procedure in functional computing: It applies a function successively to all elements in a given collection (or even several collections). WSData features a map-method that applies a block of successively to all elements in the collection (this feature is only supported on iOS 4.0 and later).

The following code demonstrates a simple transformation of individual objects:

    WSData *mapData1 = [WSData data:scientificData
                                map:^WSDatum *(const id datum) {
                                    WSDatum *newDatum = [datum copy];
                                    [newDatum setValue:2.0*[(WSDatum *)datum value]];
                                    
                                    return [newDatum autorelease];
                                }];

The block takes a single instance of WSDatum as input and outputs a new datum with two times the former content of value.

The following example shows how to use the map-method with two instances of WSDatum (which need to be of equal length):

    WSData *mapData2 = [WSData data:[NSArray arrayWithObjects:scientificData,
                                     mapData1, nil]
                                map:^WSDatum *(const id iData) {
                                    WSDatum *val1 = [iData objectAtIndex:0];
                                    WSDatum *val2 = [iData objectAtIndex:1];
                                    WSDatum *newDatum = [WSDatum datum];
                                    [newDatum setValue:([val1 value] +
                                                        [val2 value])];
                                    [newDatum setValueX:[val1 valueX]];
                                    
                                    return newDatum;
                                }];

This map takes the two input instances of WSDatum and creates a new instance with the sum of each individual value as new value and the valueX of the first instance as the new valueX. In this way, functions with a large (albeit fixed) number of arguments can be used for map.

Sorting and filtering data

Instances of WSData can be sorted with a custom comparator block. The implementation is very similar to that of the sort-mechanism of NSArray. The following line of code will return a new instance sorted by valueX of the contained WSDatum objects:

    WSData *sort2 = [mapData2 sortedDataUsingComparator:^NSComparisonResult(WSDatum *val1,
                                                                            WSDatum *val2) {
        if ([val1 valueX] < [val2 valueX]) {
            return NSOrderedAscending;
        } else if ([val1 valueX] > [val2 valueX]) {
            return NSOrderedDescending;
        }

        return NSOrderedSame;
    }];

In the same way, the method filteredDataUsingFilter: allows to create a new data set containing only those elements that match a custom condition inside the filter block (see the header file WSDataOperations.h for further details).

Reduction operations on data

At this moment, only the global sum and the global average are available as reduction operations on WSData. Note that these operations only operate on value and valueX and that errors and correlations are ignored. This is an example that gets the total and the average of value of a given data set:

    NAFloat redAv = [[scientificData reduceAverage] value];
    NAFloat redSum = [[scientificData reduceSum] value];

Using the given methods it is also possible to implement custom operations that are not yet covered by the default implementation. The reference for documentation in this case are the header files of all classes, particularly WSData.h, WSDatum.h and WSDataOperations.h.