data-frame
(require data-frame) | package: data-frame |
A data frame is a data structure used to hold data in tables with rows and columns. It is meant for conveninent access and manipulation of relatively large data sets that fit in the process memory. The package also provides functions for loading and saving data from data frames from various formats, as well as several utilities and helper functions for statistical calculations, plotting and curve fitting.
The package was initially written to support data the data processing needs for ActivityLog2, and since that application mostly deals with time series based data, the package supports data which is naturally ordered (whether by a timestamp or some other data column). However, the package does support more general data which is not necessarily ordered and also supports efficient lookups using secondary indexes, allowing multiple traversal orders to be defined for the data.
With data science a popular topic, a package named data-frame inevitably brings the question of how does it compare with other implementations with similar names and descriptions. The package author does not know the answer to that question, except to say that any resemblance to other implementatins is purely coincidental.
1 Creating data frames
The functions below allow constructing new data frames. They are mainly intended for writing functions that load data into data frames from different sources, rather for direct use in other programs. To load data into data frames, see df-read/csv or df-read/sql and for manually creating data frames, for/data-frame.
procedure
(data-frame? df) → boolean?
df : any/c
procedure
(make-data-frame [#:series series]) → data-frame?
series : (listof series?) = null
If series is null, the data frame will not contain any series, and its size will be determined by the first series added, see also df-add-series!.
procedure
(make-series name #:data data #:cmpfn cmpfn #:na na #:contract contractfn) → series? name : string? data : vector? cmpfn : (or/c #f (-> any/c any/c boolean?)) na : any/c contractfn : (-> any/c boolean?)
cmpfn specifies an ordering function to use. If present, values can be looked up in this series using df-index-of and df-lookup. The data must be ordered according to this function
na specifies the "not available" value for this series, by default it is #f.
contractfn is a contract function. If present, all values in the data series, except NA values, must satisfy this contract, that is, the function must return #t for all values in the series.
procedure
(df-add-series! df series) → any/c
df : data-frame? series : series?
If the data frame already contains a series with the same name, that series will be replaced.
See also df-row-count and make-series.
procedure
(df-del-series! df name) → any/c
df : data-frame? name : string?
procedure
(df-rename-series! df old-name new-name) → any/c
df : data-frame? old-name : string? new-name : string?
This operation will materialize all lazy series (see df-add-lazy!), making it a possibly costly operation if you have lazy series. Indices using old-name will also be updated to use new-name, but this operation is fast, and no re-indexing will be done.
procedure
(df-add-derived! df name base-series value-fn) → any/c df : data-frame? name : string? base-series : (listof string?) value-fn : mapfn/c
If a series named name already exists in the data frame, it will be replaced.
procedure
(df-add-lazy! df name base-series value-fn) → any/c
df : data-frame? name : string? base-series : (listof string?) value-fn : mapfn/c
See df-add-derived! for the parameter names.
procedure
df : data-frame?
procedure
(df-series-names df) → (listof string?)
df : data-frame?
procedure
(df-contains? df series ...) → boolean?
df : (data-frame?) series : string?
procedure
(df-contains/any? df series ...) → boolean?
df : (data-frame?) series : string?
procedure
(df-duplicate-series df name) → series?
df : data-frame? name : string?
If the series with name name is delayed, see df-add-lazy!, the series will be materialized first.
procedure
(df-set-sorted! df name cmpfn) → any/c
df : data-frame? name : string? cmpfn : (or/c #f (-> any/c any/c boolean?))
If the data in the series is not already sorted, and you want to lookup values using df-index-of or df-lookup, consider adding a secondary index using df-add-index!.
procedure
(df-is-sorted? df series) → boolean?
df : data-frame? series : string?
procedure
(df-set-contract! df name contractfn) → any/c
df : data-frame? name : string? contractfn : (or/c #f (-> any/c boolean?))
procedure
(df-shallow-copy df) → data-frame
df : data-frame?
syntax
(for/data-frame (series-name ...) (for-clause ...) body-or-break ... body)
syntax
(for*/data-frame (series-name ...) (for-clause ...) body-or-break ... body)
> (define df (for/data-frame (ints strs) ([int (in-range 5)] [str (in-list (list "a" "b" "c" "d" "e"))]) (values int str))) > (df-select df "ints") '#(0 1 2 3 4)
> (df-select df "strs") '#("a" "b" "c" "d" "e")
> (define df (for*/data-frame (ints strs) ([int (in-range 2)] [str (in-list (list "a" "b"))]) (values int str))) > (df-select df "ints") '#(0 0 1 1)
> (df-select df "strs") '#("a" "b" "a" "b")
2 Loading and Saving Data
The functions construct data frames by loading data from CSV files or by running an SQL query.
procedure
(df-read/csv input [ #:headers? headers?] #:na na [ #:quoted-numbers? quoted-numbers?]) → data-frame? input : (or/c path-string? input-port?) headers? : boolean? = #t na : (or/c string? (-> string? boolean?) "") quoted-numbers? : boolean? = #f
na represents the value in the CSV file that represents the "not available" value in the data frame. Strings equal? to this value will be replaced by #f. Alternatively, this can be a function which tests a string and returns #t if the string represents a NA value
When quoted-numbers? is #t, all quoted values in the CSV file will be converted to numbers, if possible. E.g. a value like "123" will be converted to the number 123 if quoted-numbers? is #t, but will remain the string "123" if the parameter is #f.
procedure
(df-write/csv df output #:start start #:stop stop series ...) → any/c df : data-frame? output : (or/c path-string? output-port?) start : exact-nonnegative-integer? stop : exact-nonnegative-integer? series : string?
start and stop denote the beginning and end rows to be written out, by default all rows are written out.
procedure
(df-read/sql db query param ...) → data-frame?
db : connection? query : (or/c string? virtual-statement?) param : any/c
3 Inspecting data series
procedure
(df-describe df) → any/c
df : data-frame?
4 Working With Properties
A data frame can have arbitrary data attached to it the form of key-value pairs, where the keys are symbols. This is usefull for attaching additional meta-data to data frames. The functions allow working with properties.
procedure
(df-property-names df) → (listof symbol?)
df : data-frame?
procedure
(df-put-property! df key value) → any/c
df : data-frame? key : symbol? value : any/c
procedure
(df-get-property df key [default]) → any/c
df : data-frame? key : symbol? default : any/c = (lambda () #f)
procedure
(df-del-property! df key) → any/c
df : data-frame? key : symbol?
5 NA Values
Data series support the concept that a value "not available". This is done using a special value, usually #t, but separate for each data series. The functions below allow working with “NA” values. The NA value is specified when the series is created using make-series.
procedure
(df-count-na df series) → exact-nonnegative-integer?
df : data-frame? series : string?
procedure
df : data-frame? series : string? value : any/c
procedure
(df-has-na? df series) → boolean?
df : data-frame? series : string?
procedure
(df-has-non-na? df series) → boolean?
df : data-frame? series : string?
procedure
(df-na-value df series) → any/c
df : data-frame? series : string?
6 Get and Set Individual Values
procedure
df : data-frame? position : index/c series : string?
procedure
df : data-frame? position : index/c series : string?
procedure
df : data-frame? position : index/c value : any/c series : string?
7 Indexing And Row Lookup
procedure
(df-add-index! df name series lt [ #:na-in-front? na-in-front?]) → any/c df : data-frame? name : string? series : string? lt : (-> any/c any/c boolean?) na-in-front? : boolean? = #f
procedure
(df-add-index*! df name series lt [ #:na-in-front? na-in-front?]) → any/c df : data-frame? name : string? series : (listof string?) lt : (listof (-> any/c any/c boolean?)) na-in-front? : boolean? = #f
df-add-index! will create an index on a single series and use the lt function for comparing elements, this function must provide a strict less-than ordering, and suitable values would be < for numbers and string<? for strings, although any function can be defined.
The na-in-front? determines where the “NA” values in the series are placed. If it is #t, they are placed before all other values, otherwise they are placed at the end.
The df-add-index*! will define a multi-column index on all the series specified as a list, the lt parameter fot this function is a list of comparison functions, one for each columns. Such an index will sort by the first column, than, for all equal values in the first column, it will sort on the second column, and so on.
A multi-column index is mosty used for defining a multi-column iteration order, however, such an index can still be used for fast lookup for elements in the first indexed series.
procedure
(df-del-index! df name) → any/c
df : data-frame? name : string?
procedure
(df-index-names df) → (listof string?)
df : data-frame?
procedure
(df-index-series df name) → (listof string?)
df : data-frame? name : string?
procedure
(df-index-of df series value #:exact-match? exact-match?) → index/c df : data-frame? series : string? value : any/c exact-match? : #f
procedure
(df-index-of* df series #:exact-match? exact-match? value ...) → (listof index/c) df : data-frame? series : string? exact-match? : #f value : any/c
The series must either be sorted, see df-set-sorted!, or an index must be defined for it, see df-add-index! and df-add-index*!, otherwise the calls will raise an error.
exact-match? defines what to do when the value(s) are not found in the data series. If it is #t and the value is not found, the functions return #f.
If exact-match? is #f, the value need not be present in the series, in that case, the returned index is the position of the first element which comes after the value, according to the sort function. This is the position where value could be inserted and still keep the series sorted. A value of 0 is returned if value is less or equal than the first value of the series and a value of (df-row-count df) is returned if the value is greater than all the values in series.
procedure
(df-equal-range df series value) →
index/c index/c df : data-frame? series : string? value : any/c
The series must be sorted (see df-set-sorted!), or else this will error.
The given value need not be present in the series. If this is the case, the lower bound and upper bound are the same and represent the position of the first element which comes before value, according to the sort function. This is the position in which the given value could be inserted and keep the series sorted.
procedure
(df-all-indices-of df series value) → (listof index/c)
df : data-frame? series : string? value : any/c
The series must be either sorted, see df-set-sorted!, or have an index defined for it, see df-add-index! and df-add-index*!, otherwise an error is reported.
procedure
(df-lookup df base-series series value #:exact-match? exact-match?) → any/c df : data-frame? base-series : string? series : (or/c string? (listof string?)) value : any/c exact-match? : #f
procedure
(df-lookup* df base-series series #:exact-match? exact-match? value ...) → list? df : data-frame? base-series : string? series : (or/c string? (listof string?)) exact-match? : #f value : any/c
df-lookup* allows looking up multiple values and will return a list of the corresponding values.
These functions combine df-index-of and df-ref into a single function and has the same restrictions as df-index-of: the series must either be sorted or an index defined for it.
exact-match? has the same meaning as for df-index-of
procedure
(df-lookup/interpolated df base-series series value #:interpolate interpolate [ lambda]) → any/c df : data-frame? base-series : string? series : (or/c string? (listof string?)) value : any/c interpolate : (-> real? any/c any/c any/c) lambda : (t v1 v2) = (+ (* t v1) (* (- 1 t) v2))
An interpolation function can be specified, if the default one is not sufficient. This function is called once for each value resulting series (i.e. it interpolates values one by one).
8 Extracting Data
procedure
(df-select df series [ #:filter filter #:start start #:stop stop]) → vector? df : data-frame? series : string? filter : (or/c #f (-> any/c any/c)) = #f start : index/c = 0 stop : index/c = (df-row-count df)
procedure
(df-select* df [ #:filter filter #:start start #:stop stop] series ...) → vector? df : data-frame? filter : (or/c #f (-> any/c any/c)) = #f start : index/c = 0 stop : index/c = (df-row-count df) series : string?
start and stop indicate the first and one-before-last row to be selected. filter, when present, will filter values selected: only values for which the function returns #t will be added to the resulting vector.
If there is no filter specified, the resulting vector will have (- stop start) elements. If there is a filter, the number of elements depends on how many are filtered out by this function.
procedure
(df-select/by-index df series #:index index-name [ #:from from #:to to #:filter filter]) → vector? df : data-frame? series : string? index-name : string? from : any/c = #f to : any/c = #f filter : (or/c #f (-> any/c any/c)) = #f
procedure
(df-select/by-index* df series #:index index-name [ #:from from #:to to #:filter filter]) → vector? df : data-frame? series : string? index-name : string? from : (listof any/c) = #f to : (listof any/c) = #f filter : (or/c #f (-> any/c any/c)) = #f
procedure
(df-select*/by-index df #:index index-name [ #:from from #:to to #:filter filter] series ...) → vector? df : data-frame? index-name : string? from : any/c = #f to : any/c = #f filter : (or/c #f (-> any/c any/c)) = #f series : string?
procedure
(df-select*/by-index* df #:index index-name [ #:from from #:to to #:filter filter] series ...) → vector? df : data-frame? index-name : string? from : (listof any/c) = #f to : (listof any/c) = #f filter : (or/c #f (-> any/c any/c)) = #f series : string?
HINT: to select data from all rows where a colunm has a specified value, define an index for the column and select using the same value for both from and to.
A multi-column index can also be iterated using a single value, in which case only the first column of the index is used. For example, if you have a data frame with Country, City and CityPopulation series, you can define an index on Country and CityPopulation, than select City and CityPopulation on that index with a specific country as the from and to arguments. This will return all cities in that country ordered by their population.
The by-index* versions of these functins allow specifying a multi-value key for a multivalue index.
procedure
(valid-only item) → boolean?
item : any/c
The function returns #t if all elements of item (which can be a vector or a list) are not #f.
9 Iterating Over Rows
procedure
(df-map df series fn [ #:start start #:stop stop]) → vector? df : data-frame? series : (or/c string? (listof string?)) fn : mapfn/c start : index/c = 0 stop : index/c = (df-row-count df)
procedure
(df-map/by-index df series fn #:index index-name [ #:from from #:to to]) → vector? df : data-frame? series : (or/c string? (listof string?)) fn : mapfn/c index-name : string? from : any/c = #f to : any/c = #f
procedure
(df-map/by-index* df series fn #:index index-name [ #:from from #:to to]) → vector? df : data-frame? series : (or/c string? (listof string?)) fn : mapfn/c index-name : string? from : (listof any/c) = #f to : (listof any/c) = #f
fn is a function of ether one or two arguments. If fn is a function with one argument, it is called with the values from all series as a single vector. If fn is a function of two arguments, it is called with the current and previous set of values, as vectors (this allows calculating "delta" values). I.e. fn is invoked as (fn prev current). If fn accepts two arguments, it will be invoked as (fn #f current) for the first element of the iteration.
df-map will iterate over rows in the data frame between start and stop positions, while df-map/by-index and df-map/by-index* will iterate in the order defined by the index index-name between from and to values in the indexed series.
See df-select/by-index* for a discution on the differneces between by-index and by-index* versions.
procedure
(df-for-each df series fn [ #:start start #:stop stop]) → void df : data-frame? series : (or/c string? (listof string?)) fn : mapfn/c start : index/c = 0 stop : index/c = (df-row-count df)
procedure
(df-for-each/by-index df series fn #:index index-name [ #:from from #:to to]) → void df : data-frame? series : (or/c string? (listof string?)) fn : mapfn/c index-name : string? from : any/c = #f to : any/c = #f
procedure
(df-for-each/by-index* df series fn #:index index-name [ #:from from #:to to]) → void df : data-frame? series : (or/c string? (listof string?)) fn : mapfn/c index-name : string? from : (listof any/c) = #f to : (listof any/c) = #f
procedure
(df-fold df series init-value fn [ #:start start #:stop stop]) → any/c df : data-frame? series : (or/c string? (listof string?)) init-value : any/c fn : foldfn/c start : index/c = 0 stop : index/c = (df-row-count df)
procedure
(df-fold/by-index df series init-value fn #:index index-name [ #:from from #:to to]) → any/c df : data-frame? series : (or/c string? (listof string?)) init-value : any/c fn : foldfn/c index-name : string? from : any/c = #f to : any/c = #f
procedure
(df-fold/by-index* df series init-value fn #:index index-name [ #:from from #:to to]) → any/c df : data-frame? series : (or/c string? (listof string?)) init-value : any/c fn : foldfn/c index-name : string? from : (listof any/c) = #f to : (listof any/c) = #f
fn is a function of ether two or three arguments. If fn is a function with two arguments, it is called with the fold value plus the values from all series is passed in as a single vector. If fn is a function of three arguments, it is called with the fold value plus the current and previous set of values, as vectors (this allows calculating "delta" values). I.e. fn is invoked as (fn val prev current). If fn accepts two arguments, it will be invoked as (fn init-val #f current) for the first element of the iteration.
df-fold will iterate over rows in the data frame between start and stop positions, while df-fold/by-index and df-fold/by-index* will iterate in the order defined by the index index-name between from and to values in the indexed series.
See df-select/by-index* for a discution on the differneces between by-index and by-index* versions.
procedure
(in-data-frame df [ #:start start #:stop stop] series ...) → sequence? df : data-frame? start : index/c = 0 stop : index/c = (df-row-count df) series : string?
procedure
(in-data-frame/as-list df [ #:start start #:stop stop] series ...) → sequence? df : data-frame? start : index/c = 0 stop : index/c = (df-row-count df) series : string?
procedure
(in-data-frame/as-vector df [ #:start start #:stop stop] series ...) → sequence? df : data-frame? start : index/c = 0 stop : index/c = (df-row-count df) series : string?
This is intended to be used in for and related constructs to iterate over elements in the data frame:
(for (([lat lon] (in-data-frame df "lat" "lon"))) (printf "lat = ~a, lon = ~a~%" lat lon))
The in-data-frame/as-list and in-data-frame/as-vector variants work the same, but they produce a single value, a list or a vector containing a row of values from the series:
(for ((coord (in-data-frame/as-list df "lat" "lon"))) (match-define (list lat lon) coord) (printf "lat = ~a, lon = ~a~%" lat lon))
procedure
(in-data-frame/by-index df #:index index-name [ #:from from #:to to] series ...) → sequence? df : data-frame? index-name : string? from : any/c = #f to : any/c = #f series : string?
procedure
(in-data-frame/by-index/as-list df #:index index-name [ #:from from #:to to] series ...) → sequence? df : data-frame? index-name : string? from : any/c = #f to : any/c = #f series : string?
procedure
(in-data-frame/by-index/as-vector df #:index index-name [ #:from from #:to to] series ...) → sequence? df : data-frame? index-name : string? from : any/c = #f to : any/c = #f series : string?
procedure
(in-data-frame/by-index* df #:index index-name [ #:from from #:to to] series ...) → sequence? df : data-frame? index-name : string? from : any/c = #f to : any/c = #f series : string?
procedure
(in-data-frame/by-index*/as-list df #:index index-name [ #:from from #:to to] series ...) → sequence? df : data-frame? index-name : string? from : any/c = #f to : any/c = #f series : string?
procedure
(in-data-frame/by-index*/as-vector df #:index index-name [ #:from from #:to to] series ...) → sequence? df : data-frame? index-name : string? from : any/c = #f to : any/c = #f series : string?
See df-select/by-index* for a discution on the differneces between by-index and by-index* versions and on using from and to values.
10 Statistics
The following functions allow calculating statistics on data frame series. They build on top of the math/statistics module.
procedure
(df-set-default-weight-series! df series) → any/c
df : data-frame? series : (or/c #f string?)
procedure
(df-get-default-weight-series df) → (or/c #f string?)
df : data-frame?
A weight series needs to be used when samples in the data frame don’t have equal weight. For example, if a parameter (e.g. heart rate) is recorded at variable intervals, simply averaging the values will not produce an accurate average, if a timer series is also present, it can be used as a weight series to produce a better average.
procedure
(df-statistics df series [ #:weight-series weight-series #:start start #:stop stop]) → (or/c #f statistics?) df : data-frame? series : string? weight-series : string? = (df-get-default-weight-series df) start : exact-nonnegative-integer? = 0 stop : exact-nonnegative-integer? = (df-row-count df)
procedure
(df-quantile df series #:weight-series string? [ #:less-than less-than] qvalue ...) → (or/c #f (listof real?)) df : data-frame? series : string? string? : (df-get-default-weight-series df) less-than : (-> any/c any/c boolean?) = < qvalue : (between/c 0 1)
11 Least Squares Fitting
struct
(struct least-squares-fit (type coefficients residual fn) #:extra-constructor-name make-least-squares-fit) type : (or/c 'linear 'polynomial 'power 'exponential 'logarithmic) coefficients : (listof real?) residual : (or/c #f real?) fn : (-> real? real?)
procedure
(df-least-squares-fit df xseries yseries [ #:start start #:stop stop #:mode mode #:polynomial-degree degree #:residual? residual? #:annealing? annealing? #:annealing-iterations iterations]) → least-squares-fit? df : data-frame? xseries : string? yseries : string? start : exact-nonnegative-integer? = 0 stop : exact-nonnegative-integer? = (df-row-count df)
mode : (or/c 'linear 'polynomial 'poly 'power 'exponential 'exp 'logarithmic 'log) = 'linear degree : exact-nonnegative-integer? = 2 residual? : boolean? = #f annealing? : boolean? = #f iterations : exact-nonnegative-integer? = 500
start and stop specify the start and end position in the series, by default all values are considered for the fit.
mode determines the type of the function being fitted and can have one of the following values:
'linear – a function Y = a * X + b is fitted where ’a’ and ’b’ are fitted; this is equivalent of fitting a ’polynomial of degree 1 (see below)
'polynomial or 'poly – a polynomial Y = a0 + a1 * X + a2 * X^2 + ... is fitted. The degree of the polynomial is specified by the degree parameter, by default this is 2.
'exponential or 'exp – a function of Y = a * e ^ (b * X) + c is fitted. Note that this fit is not very good, and annealing needs to be used to improve it (see below)
'logarithmic or 'log – a function of type Y = a + b * ln(X) is fitted. This will only return a "real" fit function (as opposed to an imaginary one) if all values in YSERIES are positive
'power – a function of type Y = a * X ^ b is fitted. This will only return a "real" fit function (as opposed to an imaginary one) if all values in YSERIES are positive. Note that this fit is not very good, and annealing needs to be used to improve it (see below)
residual? when #t indicates that the residual value is also returned in the ‘least-squares-fit‘ structure. Setting it to #f will avoid some unnecessary computations.
annealing? when #t indicates that the fit coefficients should be further refined using the annealing function. This is only used for 'exponential or
'power fit functions as these ones do not produce "best fit" coefficients – I don’t know why, I am not a mathematician, I only used the formulas. Using annealing will significantly improve the fit for these functions, but will still not determine the best one. Note that the annealing algorithm is probabilistic, so applying it a second time on the same arguments will produce a slightly different result.
iterations represents the number of annealing iterations, see the #:iterations parameter to the ‘annealing‘ function.
12 Histograms and histogram plots
procedure
(df-histogram df series [ #:weight-series weight-series #:bucket-width bucket-width #:trim-outliers trim-outliers #:include-zeroes? include-zeroes? #:as-percentage? as-percentage?] #:start start #:stop stop) → (or/c #f histogram/c) df : data-frame? series : string?
weight-series : (or/c #f string?) = (df-get-default-weight-series df) bucket-width : real? = 1 trim-outliers : (or/c #f (between/c 0 1)) = #f include-zeroes? : boolean? = #t as-percentage? : boolean? = #f start : exact-nonnegative-integer? stop : exact-nonnegative-integer?
weight-series specifies the series to be used for weighting the samples. By default, it it uses the 'weight property stored in the data-frame, see df-set-default-weight-series!. Use #f for no weighting, in this case, each sample will have a weight of 1.
bucket-width specifies the width of each histogram slot. Samples in the data series are grouped together into slots, which are from 0 to bucket-width, than from bucket-width to (* 2 bucket-width) and so on. The bucket-width value can be less than 1.0.
trim-outliers specifies to remove slots from both ends of the histogram that contain less than the specified percentage of values. When #f on slots are trimmed.
include-zeroes? specifies whether samples with a slot of 0 are included in the histogram or not. Note that slot 0 contains samples from 0 to bucket-width.
as-percentage? determines if the data in the histogram represents a percentage (all ranks add up to 100) or it is the rank of each slot.
In the resulting histogram, samples that are numbers or strings will be sorted. In addition, if the samples are numbers, empty slots will be created so that the buckets are also consecutive.
procedure
(histogram-renderer histogram [ #:color color #:skip skip #:x-min x-min #:label label #:blank-some-labels blank-some-labels? #:x-value-formatter formatter]) → (treeof renderer2d?) histogram : histogram/c color : any/c = #f skip : real? = (discrete-histogram-skip) x-min : real? = 0 label : string? = #f blank-some-labels? : boolean? = #t formatter : (or/c #f (-> number? string?)) = #f
color determines the color of the histogram bars.
label specifies the label to use for this plot renderer.
skip and x-min are used to plot dual histograms, see histogram-renderer/dual.
All the above arguments are sent directly to the discrete-histogram
blank-some-labels?, controls if some of the labels are blanked out if the plot contains too many values, this can produce a nicer looking plot.
formatter controls how the histogram values are displayed. By default, labels for the values are displayed with ~a, but this function can be used for custom formatter. For example, if the values in the histogram represent running pace, the formatter can transform a value of 300 into the label "5:00".
procedure
(combine-histograms h1 h2) → combined-histogram/c
h1 : histogram/c h2 : histogram/c
procedure
(histogram-renderer/dual combined-histogram label1 label2 [ #:color1 color1 #:color2 color2 #:x-value-formatter formatter]) → (treeof renderer2d?) combined-histogram : combined-histogram/c label1 : string? label2 : string? color1 : any/c = #f color2 : any/c = #f formatter : (or/c #f (-> number? string?)) = #f
label1 and color1 represent the label and colors for the first histogram, label2 and color2 represent the label and colors to use for the second histogram.
formatter controls how the histogram values are displayed. By default, labels for the values are displayed with ~a, but this function can be used for custom formatter. For example, if the values in the histogram represent running pace, the formatter can transform a value of 300 into the label "5:00".
procedure
(histogram-renderer/factors histogram factor-fn factor-colors [ #:x-value-formatter formatter]) → (treeof renderer2d?) histogram : histogram/c factor-fn : (-> real? symbol?) factor-colors : (listof (cons/c symbol? color/c)) formatter : (or/c #f (-> number? string?)) = #f
formatter controls how the histogram values are displayed. By default, labels for the values are displayed with ~a, but this function can be used for custom formatter. For example, if the values in the histogram represent running pace, the formatter can transform a value of 300 into the label "5:00".
13 GPX Files
(require data-frame/gpx) | package: data-frame |
This module provides functions for reading and writing data frames using the GPS Exchange Format (GPX).
procedure
(df-read/gpx input) → data-frame?
input : (or/c path-string? input-port?)
"lat" and "lon" series representing the latitude and longitude of each point
"timestamp" series representing the UTC timestamp in seconds for each point. The series will also be marked as sorted, if it is actually sorted
"dst" representing a distance from the start. If distance data is not present in the GPX file, this series will be calculated from the GPX coordiantes. The series will be marked as sorted, if it is actually sorted
"hr" representing heart rate measurements
"cad" representing cadence measurements
"pwr" representing power measurements, in watts
"spd" representing the speed
The data frame will also have the following properties:
a 'name property containing the name of the track segment, if this is present in the GPX file.
a 'waypoints property containing a list of waypoints, if they GPX track has any. Each waypoint is represented as a list of TIMESTAMP, LAT, LON, ELEVATION and NAME
a 'laps property containing a list of timestamps corresponding to each way point in the waypoint list – the laps property cannot be constructed correctly if the waypoints are missing a timestamp property.
All the track segments in the GPX file will be concatenated.
procedure
(df-write/gpx df output [ #:name name #:extra-series extra-series #:start start #:stop stop]) → any/c df : data-frame? output : (or/c path-string? output-port?) name : (or/c #f string?) = #f
extra-series : (listof string?) = '("hr" "cad" "pwr" "spd" "dst") start : exact-nonnegative-integer? = 0 stop : exact-nonnegative-integer? = (df-row-count df)
The data frame is expected to contain the "timestamp", "lat", "lon" series, and optionally "alt" or "calt" (corrected altitude) series. In addition to these series, optional heart rate, cadence, speed, power and distance data can also be written out by specifying a list of series names in extra-series, series which don’t exist will be silently discarded. Series which exist, but we don’t know how to write them out are also silently discarded (e.g. no "gpxdata:" tag)
The entire GPS track is exported as a single track segment, unless start and stop positions are specified, in which case only data between these positions is exported (this can be used to export a subset of the data)
The laps property, if present, is assumed to contain a list of timestamps and the positions corresponding to these timestamps are exported as way points.
The name of the segment can be specified as the name parameter. If this is #f, the 'name property in the data frame is consulted, if that one is missing a default track name is used.
14 TCX Files
(require data-frame/tcx) | package: data-frame |
This module provides functions for reading Training Center XML (TCX) files into data frames.
procedure
(df-read/tcx input) → data-frame?
input : (or/c path-string? input-port?)
"lat" and "lon" series representing the latitude and longitude of each point
"timestamp" series representing the UTC timestamp in seconds for each point. The series will also be marked as sorted, if it is actually sorted
"dst" representing a distance from the start. If distance data is not present in the GPX file, this series will be calculated from the GPX coordiantes. The series will be marked as sorted, if it is actually sorted
"hr" representing heart rate measurements
"cad" representing cadence measurements
"pwr" representing power measurements, in watts
"spd" representing the speed
The data frame may also have the following properties (if they are present in the TCX document):
'unit-id the serial number of the device which recorded the activity.
'product-id the product id for the device that recorded the activity (indentifies the device type)
'sport the sport for the activity. This is a free form string, but TCX format usualy uses the strings "Running" for running activities and "Biking" for biking activities.
a 'laps property containing a list of timestamps corresponding to the start of each lap in the activity.
procedure
(df-read/tcx/multiple input) → (listof data-frame?)
input : (or/c path-string? input-port?)