{"_id":"564a629ceed7de0d003671d3","project":"55773a5ba042551900b002cb","__v":7,"version":{"_id":"55773a5ba042551900b002ce","project":"55773a5ba042551900b002cb","__v":17,"createdAt":"2015-06-09T19:11:23.764Z","releaseDate":"2015-06-09T19:11:23.764Z","categories":["55773a5ca042551900b002cf","55773a6ce6063e0d00481380","55773ab007e7110d001043ec","55773abaa042551900b002d5","55773ac207e7110d001043ed","55773acb07e7110d001043ee","55773ad3a042551900b002d6","55773adce6063e0d00481383","55773ae4a042551900b002d7","55773af307e7110d001043ef","55773af907e7110d001043f0","55773b0407e7110d001043f1","563a4f7ad25e8919005f3f39","563a4fcaa19edf0d00972321","564a70dc4cd0521700523edf","564b797bcc472d0d00da9435","564b855b766d4923004e1fd1"],"is_deprecated":false,"is_hidden":false,"is_beta":false,"is_stable":true,"codename":"Motherboard","version_clean":"1.0.0","version":"1"},"category":{"_id":"55773a6ce6063e0d00481380","pages":["55773a9fe6063e0d00481381","5638e57daaddb90d00c75fd1","564a61684cd0521700523ebf","564a629ceed7de0d003671d3","564a631a4721851900a675dc","564a63974721851900a675e4","564a63e8eed7de0d003671d7","564a9eb4e5d9d61700d57fe7","566731f5d784a70d00397cd4"],"project":"55773a5ba042551900b002cb","version":"55773a5ba042551900b002ce","__v":9,"sync":{"url":"","isSync":false},"reference":true,"createdAt":"2015-06-09T19:11:40.967Z","from_sync":false,"order":9,"slug":"statistics-api","title":"Statistics API"},"user":"546d17e2eb9cfd1400dd4529","parentDoc":null,"updates":[],"next":{"pages":[],"description":""},"createdAt":"2015-11-16T23:11:24.203Z","link_external":false,"link_url":"","githubsync":"","sync_unique":"","hidden":false,"api":{"results":{"codes":[]},"settings":"","auth":"required","params":[],"url":""},"isReference":true,"order":0,"body":"The Statistics API allows rich querying for Triathlon.org results. Whilst it is possible to replicate these queries by combining other API calls the statistics endpoints offered provide quick access to a [data store optimized for such analytics](https://keen.io).\n[block:image]\n{\n  \"images\": [\n    {\n      \"image\": [\n        \"https://files.readme.io/VNDMFettT7SfWryU6LRz_49qyzv5TlG3q7nBrlVgL%20(1).jpg\",\n        \"49qyzv5TlG3q7nBrlVgL (1).jpg\",\n        \"850\",\n        \"315\",\n        \"#43a0ec\",\n        \"\"\n      ],\n      \"caption\": \"Use the API to find how often these athletes have shared the podium\",\n      \"sizing\": \"full\"\n    }\n  ]\n}\n[/block]\nFor example the following API call returns a medal breakdown for Javier Gomez (athlete.id 5695) for the current 12 months. With advanced filtering and grouping available for a multitude of parameters the statistics API can generate a huge array of analysis types. You may see the '[Getting Started](getting-started)' section for further inspiration.\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"curl --header \\\"apikey: [[app:key]]\\\" \\\"https://api.triathlon.org/v1/statistics/results?analysis=count&group_by=position|event.name&timeframe=this_12_months&filters=athlete.id%2Ceq%2C5695|position%2Clte%2C3\\\"\",\n      \"language\": \"curl\"\n    }\n  ]\n}\n[/block]\n\n[block:callout]\n{\n  \"type\": \"info\",\n  \"title\": \"Data types\",\n  \"body\": \"To facilitate the multiple analysis types listed below such as average and minimum all times are stored in seconds and will need to be converted on output if you wish to display a different format.\"\n}\n[/block]\n\n[block:api-header]\n{\n  \"type\": \"basic\",\n  \"title\": \"Available Results\"\n}\n[/block]\nCurrently only World Triathlon Series and Olympic Games results are available for analysis. All results will be added shortly.\n[block:api-header]\n{\n  \"type\": \"basic\",\n  \"title\": \"Analysis Types\"\n}\n[/block]\n* **count** - counts the number of items recorded that meet the criteria that you provide\n* **count_unique** - counts the number of items that have a unique value for a given property\n* **minimum** - finds the minimum numeric value for a given property. All non-numeric values are ignored as part of the analysis\n* **maximum** - finds the maximum numeric value for a given property. All non-numeric values are ignored as part of the analysis\n* **sum ** - finds the sum of all numeric values for a given property. All non-numeric values are ignored as part of the analysis\n* **average** - finds the average value for a given property. All non-numeric values are ignored as part of the analysis\n* **median** - finds the median value for a given property. All non-numeric values are ignored as part of the analysis\n* **percentile** - finds the specified percentile value for a given property. All non-numeric values are ignored as part of the analysis\n[block:api-header]\n{\n  \"type\": \"basic\",\n  \"title\": \"Filters\"\n}\n[/block]\nFilters are used to restrict queries and all filters excluding **geo-filters** (see below) contain the following properties. The **in** filter accepts optional additional parameters (see below).\n\n* **property_name**  - see event properties of the chosen endpoint for all available properties and types\n* **operator** - which filter to perform (see list below for available options)\n* **property_value** - the value of the property filter\n\nHere is a list of operators you can use with filters:\n\n* **eq **- Equal to e.g. filters=athlete.last,eq,Jorgensen\n* **ne** - Not equal to e.g. filters=athlete.last,ne,Brownlee\n* **lt** - Less than e.g. filters=position,lt,10\n* **lte **- Less than or equal to e.g. filters=position,lte,10\n* **gt **- Greater than e.g. filters=position,gt,3\n* **gte **- Greater than or equal to e.g. filters=position,gte,3\n* **exists** - Whether or not a specific property exists on an event record. When using the “exists” operator, the value passed in must be either “true” or “false” e.g. filters=swim.time,exists,false\n* **in **- Whether or not the property value is in a given set of values e.g. filters=athlete_name,in,Jorgensen,Brownlee,Mola\n* **contains** - Whether or not the string property value contains the given set of characters e.g. filters=event.name,contains,Hamburg\n* **within **- Used to select events within a certain radius of the provided geo coordinate (for geo analysis only) e.g. filters=location,within,30,37.77479,-122.42005\n[block:callout]\n{\n  \"type\": \"info\",\n  \"title\": \"Use id properties where available\",\n  \"body\": \"Athlete's may change names whereas ids are fixed. Favour the use of ids over name properties where possible.\"\n}\n[/block]\nBecause not all filter operators make sense for the different property data types, only certain ones are valid for each type.\n\n* **string **- eq, ne, lt, gt, exists, in, contains, not_contains\n* **number **- eq, ne, lt, lte, gt, gte, exists, in\n* **boolean** - eq, exists, in\n* **geo coordinates** - within\n\nFilters are constructed by generating a comma seperated string of the property_name, operator and property_value. Multiple filters may be applied and be separated by a pipe. To avoid issues when sending queries it is recommended that you url-encode your filter string before your request is made. \n[block:callout]\n{\n  \"type\": \"warning\",\n  \"title\": \"Case Sensitive\",\n  \"body\": \"Filters are case sensitive so london is not the same as London. Beware!\"\n}\n[/block]\nFor example the following filter filters for an athlete_id of 11378 and the event name contains the string \"London\". We show the filter to be passed as well as the raw unencoded filter for clarity.\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"filters=athlete.id%2Ceq%2C11378%7Cevent_name%2Ccontains%2CLondon\",\n      \"language\": \"text\",\n      \"name\": \"Encoded Filter\"\n    },\n    {\n      \"code\": \"filters=athlete.id,eq,11378|event_name,contains,London\",\n      \"language\": \"text\",\n      \"name\": \"Raw Filter\"\n    }\n  ]\n}\n[/block]\n**Geo filters** allow you to restrict your criteria within a certain radius (in miles). These filters contain five properties:\n\n* **property_name**  - location\n* **operator** - within\n* **distance** - the distance in miles to find events\n* **latitude** - the latitude of the starting point\n* **longitude** - the longitude of the starting point\n\nThe following example limits results to 30 miles from Cape Town, South Africa.\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"filters=location%2Cwithin%2C30%2C37.77479%2C-122.42005\",\n      \"language\": \"text\",\n      \"name\": \"Encoded Geo Filter\"\n    },\n    {\n      \"code\": \"filters=location,within,30,37.77479,-122.42005\",\n      \"language\": \"text\",\n      \"name\": \"Raw Geo Filter\"\n    }\n  ]\n}\n[/block]\nThere is one limitation to geo filtering, which is that it can’t be used in combination with a group by request.\n[block:callout]\n{\n  \"type\": \"info\",\n  \"title\": \"Filtering by non-existent properties\",\n  \"body\": \"If you apply a filter that filters on non-existent properties, the filter will simply have no effect as opposed to causing an error. If you get an unexpected result double check your filters!\"\n}\n[/block]\nThe **in** filter accepts multiple property values each simply separated via an additional comma, hence the following filter filters where the athlete last name is Brownlee, Jorgensen or Snowsill. You may pass in as many values as you wish.\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"filters=athlete.last,in,Brownlee,Jorgensen,Snowsill\",\n      \"language\": \"text\",\n      \"name\": \"In filter\"\n    }\n  ]\n}\n[/block]\n\n[block:api-header]\n{\n  \"type\": \"basic\",\n  \"title\": \"Group By\"\n}\n[/block]\nThe group_by query parameter may be added to group the results by a specific property. To use group by, simply set the group_by parameter equal to the name of the property by which you want to group. For multiple group_by parameters simply provide a pipe delimited list.\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"group_by=event.name|athlete.id\",\n      \"language\": \"text\",\n      \"name\": \"Multiple group_by clauses\"\n    }\n  ]\n}\n[/block]\n\n[block:callout]\n{\n  \"type\": \"info\",\n  \"title\": \"Grouping by non-existent properties\",\n  \"body\": \"If you group_by a non-existent property, the group_by clause will return *null* for the grouped property rather than failing.\"\n}\n[/block]\n\n[block:api-header]\n{\n  \"type\": \"basic\",\n  \"title\": \"Timeframe\"\n}\n[/block]\nTimeframes are used to restrict the timeframe of the data under analysis and may be specified in two different ways:\n\n**Relative Timeframes** - a timeframe that is relative to now. (For example: this year.)\n**Absolute Timeframes** - a timeframe that is specified by two points in time provided by the query parameters start_date and end_date\n[block:callout]\n{\n  \"type\": \"info\",\n  \"title\": \"Relative vs Absolute Timeframes\",\n  \"body\": \"Absolute timeframes are recommended to avoid errors. When both relative and absolute timeframes are provided the absolute timeframe takes precedence.\"\n}\n[/block]\nRelative timeframes may be grouped into two categories: “this” and “previous”. Use “this” when you want to include events happening right up until now. Use “previous” when you only want to get results for complete chunks of time (e.g. the full month or year).\n\n* this_month\n* this_year\n* this_n_days\n* this_n_weeks\n* this_n_months\n* this_n_years\n* previous_month (same as previous_1_month)\n* previous_n_days\n* previous_n_weeks\n* previous_n_months\n* previous_n_years\n[block:callout]\n{\n  \"type\": \"warning\",\n  \"body\": \"Care must be taken when using relative timeframes and when in doubt use absolute timeframes for your analysis. For example if today is June 6th 2015 then this_month would be from June 1 - June 6 2015 inclusive and previous_month would be the May 1 - May 31 2015. \\n\\nRelative timeframes are useful for quick data extraction i.e. for all results in the calendar year of 2015 simply provide this_year which runs Jan 1-Dec 31 of the current year.\",\n  \"title\": \"Relative timeframes\"\n}\n[/block]\nAbsolute timeframes simply require the start_date and end_date parameters to be supplied in yyyy-mm-dd format.\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"timeframe=this_year\",\n      \"language\": \"text\",\n      \"name\": \"Relative Timeframe\"\n    },\n    {\n      \"code\": \"start_date=2015-01-01&end_date=2015-12-31\",\n      \"language\": \"text\",\n      \"name\": \"Absolute Timeframe\"\n    }\n  ]\n}\n[/block]\n\n[block:api-header]\n{\n  \"type\": \"basic\",\n  \"title\": \"Interval\"\n}\n[/block]\nThe interval property specifies how to breakdown timeframes into specified intervals e.g. to find results per year or quadrennial Olympic cycles. Supported intervals include:\n\n* **monthly **- breaks your timeframe into month-long chunks.\n* **yearly** - breaks your timeframe into year-long chunks.\n* **every_n_times** - breaks your timeframe into chunks according to the specified length e.g. every_4_years\n\nFor example if you wished to retrieve Alistair Brownlee's podium distribution for each year of the World Triathlon Series the following unencoded query would achieve this (note the interval=yearly) parameter.\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"curl --header \\\"apikey: [[app:key]]\\\" \\\"https://api.triathlon.org/v1/statistics/results?group_by=position&filters=athlete.id,eq,7788|position,lte,3&timeframe=this_7_years&interval=yearly\\\"\",\n      \"language\": \"curl\"\n    }\n  ]\n}\n[/block]\n\n[block:callout]\n{\n  \"type\": \"warning\",\n  \"title\": \"Intervals require a timeframe to be set\",\n  \"body\": \"You must set a timeframe to use intervals and the API will return a 400 bad request response if it not set.\"\n}\n[/block]","excerpt":"","slug":"statistics-api-overview","type":"basic","title":"Statistics API Overview"}

Statistics API Overview


The Statistics API allows rich querying for Triathlon.org results. Whilst it is possible to replicate these queries by combining other API calls the statistics endpoints offered provide quick access to a [data store optimized for such analytics](https://keen.io). [block:image] { "images": [ { "image": [ "https://files.readme.io/VNDMFettT7SfWryU6LRz_49qyzv5TlG3q7nBrlVgL%20(1).jpg", "49qyzv5TlG3q7nBrlVgL (1).jpg", "850", "315", "#43a0ec", "" ], "caption": "Use the API to find how often these athletes have shared the podium", "sizing": "full" } ] } [/block] For example the following API call returns a medal breakdown for Javier Gomez (athlete.id 5695) for the current 12 months. With advanced filtering and grouping available for a multitude of parameters the statistics API can generate a huge array of analysis types. You may see the '[Getting Started](getting-started)' section for further inspiration. [block:code] { "codes": [ { "code": "curl --header \"apikey: [[app:key]]\" \"https://api.triathlon.org/v1/statistics/results?analysis=count&group_by=position|event.name&timeframe=this_12_months&filters=athlete.id%2Ceq%2C5695|position%2Clte%2C3\"", "language": "curl" } ] } [/block] [block:callout] { "type": "info", "title": "Data types", "body": "To facilitate the multiple analysis types listed below such as average and minimum all times are stored in seconds and will need to be converted on output if you wish to display a different format." } [/block] [block:api-header] { "type": "basic", "title": "Available Results" } [/block] Currently only World Triathlon Series and Olympic Games results are available for analysis. All results will be added shortly. [block:api-header] { "type": "basic", "title": "Analysis Types" } [/block] * **count** - counts the number of items recorded that meet the criteria that you provide * **count_unique** - counts the number of items that have a unique value for a given property * **minimum** - finds the minimum numeric value for a given property. All non-numeric values are ignored as part of the analysis * **maximum** - finds the maximum numeric value for a given property. All non-numeric values are ignored as part of the analysis * **sum ** - finds the sum of all numeric values for a given property. All non-numeric values are ignored as part of the analysis * **average** - finds the average value for a given property. All non-numeric values are ignored as part of the analysis * **median** - finds the median value for a given property. All non-numeric values are ignored as part of the analysis * **percentile** - finds the specified percentile value for a given property. All non-numeric values are ignored as part of the analysis [block:api-header] { "type": "basic", "title": "Filters" } [/block] Filters are used to restrict queries and all filters excluding **geo-filters** (see below) contain the following properties. The **in** filter accepts optional additional parameters (see below). * **property_name** - see event properties of the chosen endpoint for all available properties and types * **operator** - which filter to perform (see list below for available options) * **property_value** - the value of the property filter Here is a list of operators you can use with filters: * **eq **- Equal to e.g. filters=athlete.last,eq,Jorgensen * **ne** - Not equal to e.g. filters=athlete.last,ne,Brownlee * **lt** - Less than e.g. filters=position,lt,10 * **lte **- Less than or equal to e.g. filters=position,lte,10 * **gt **- Greater than e.g. filters=position,gt,3 * **gte **- Greater than or equal to e.g. filters=position,gte,3 * **exists** - Whether or not a specific property exists on an event record. When using the “exists” operator, the value passed in must be either “true” or “false” e.g. filters=swim.time,exists,false * **in **- Whether or not the property value is in a given set of values e.g. filters=athlete_name,in,Jorgensen,Brownlee,Mola * **contains** - Whether or not the string property value contains the given set of characters e.g. filters=event.name,contains,Hamburg * **within **- Used to select events within a certain radius of the provided geo coordinate (for geo analysis only) e.g. filters=location,within,30,37.77479,-122.42005 [block:callout] { "type": "info", "title": "Use id properties where available", "body": "Athlete's may change names whereas ids are fixed. Favour the use of ids over name properties where possible." } [/block] Because not all filter operators make sense for the different property data types, only certain ones are valid for each type. * **string **- eq, ne, lt, gt, exists, in, contains, not_contains * **number **- eq, ne, lt, lte, gt, gte, exists, in * **boolean** - eq, exists, in * **geo coordinates** - within Filters are constructed by generating a comma seperated string of the property_name, operator and property_value. Multiple filters may be applied and be separated by a pipe. To avoid issues when sending queries it is recommended that you url-encode your filter string before your request is made. [block:callout] { "type": "warning", "title": "Case Sensitive", "body": "Filters are case sensitive so london is not the same as London. Beware!" } [/block] For example the following filter filters for an athlete_id of 11378 and the event name contains the string "London". We show the filter to be passed as well as the raw unencoded filter for clarity. [block:code] { "codes": [ { "code": "filters=athlete.id%2Ceq%2C11378%7Cevent_name%2Ccontains%2CLondon", "language": "text", "name": "Encoded Filter" }, { "code": "filters=athlete.id,eq,11378|event_name,contains,London", "language": "text", "name": "Raw Filter" } ] } [/block] **Geo filters** allow you to restrict your criteria within a certain radius (in miles). These filters contain five properties: * **property_name** - location * **operator** - within * **distance** - the distance in miles to find events * **latitude** - the latitude of the starting point * **longitude** - the longitude of the starting point The following example limits results to 30 miles from Cape Town, South Africa. [block:code] { "codes": [ { "code": "filters=location%2Cwithin%2C30%2C37.77479%2C-122.42005", "language": "text", "name": "Encoded Geo Filter" }, { "code": "filters=location,within,30,37.77479,-122.42005", "language": "text", "name": "Raw Geo Filter" } ] } [/block] There is one limitation to geo filtering, which is that it can’t be used in combination with a group by request. [block:callout] { "type": "info", "title": "Filtering by non-existent properties", "body": "If you apply a filter that filters on non-existent properties, the filter will simply have no effect as opposed to causing an error. If you get an unexpected result double check your filters!" } [/block] The **in** filter accepts multiple property values each simply separated via an additional comma, hence the following filter filters where the athlete last name is Brownlee, Jorgensen or Snowsill. You may pass in as many values as you wish. [block:code] { "codes": [ { "code": "filters=athlete.last,in,Brownlee,Jorgensen,Snowsill", "language": "text", "name": "In filter" } ] } [/block] [block:api-header] { "type": "basic", "title": "Group By" } [/block] The group_by query parameter may be added to group the results by a specific property. To use group by, simply set the group_by parameter equal to the name of the property by which you want to group. For multiple group_by parameters simply provide a pipe delimited list. [block:code] { "codes": [ { "code": "group_by=event.name|athlete.id", "language": "text", "name": "Multiple group_by clauses" } ] } [/block] [block:callout] { "type": "info", "title": "Grouping by non-existent properties", "body": "If you group_by a non-existent property, the group_by clause will return *null* for the grouped property rather than failing." } [/block] [block:api-header] { "type": "basic", "title": "Timeframe" } [/block] Timeframes are used to restrict the timeframe of the data under analysis and may be specified in two different ways: **Relative Timeframes** - a timeframe that is relative to now. (For example: this year.) **Absolute Timeframes** - a timeframe that is specified by two points in time provided by the query parameters start_date and end_date [block:callout] { "type": "info", "title": "Relative vs Absolute Timeframes", "body": "Absolute timeframes are recommended to avoid errors. When both relative and absolute timeframes are provided the absolute timeframe takes precedence." } [/block] Relative timeframes may be grouped into two categories: “this” and “previous”. Use “this” when you want to include events happening right up until now. Use “previous” when you only want to get results for complete chunks of time (e.g. the full month or year). * this_month * this_year * this_n_days * this_n_weeks * this_n_months * this_n_years * previous_month (same as previous_1_month) * previous_n_days * previous_n_weeks * previous_n_months * previous_n_years [block:callout] { "type": "warning", "body": "Care must be taken when using relative timeframes and when in doubt use absolute timeframes for your analysis. For example if today is June 6th 2015 then this_month would be from June 1 - June 6 2015 inclusive and previous_month would be the May 1 - May 31 2015. \n\nRelative timeframes are useful for quick data extraction i.e. for all results in the calendar year of 2015 simply provide this_year which runs Jan 1-Dec 31 of the current year.", "title": "Relative timeframes" } [/block] Absolute timeframes simply require the start_date and end_date parameters to be supplied in yyyy-mm-dd format. [block:code] { "codes": [ { "code": "timeframe=this_year", "language": "text", "name": "Relative Timeframe" }, { "code": "start_date=2015-01-01&end_date=2015-12-31", "language": "text", "name": "Absolute Timeframe" } ] } [/block] [block:api-header] { "type": "basic", "title": "Interval" } [/block] The interval property specifies how to breakdown timeframes into specified intervals e.g. to find results per year or quadrennial Olympic cycles. Supported intervals include: * **monthly **- breaks your timeframe into month-long chunks. * **yearly** - breaks your timeframe into year-long chunks. * **every_n_times** - breaks your timeframe into chunks according to the specified length e.g. every_4_years For example if you wished to retrieve Alistair Brownlee's podium distribution for each year of the World Triathlon Series the following unencoded query would achieve this (note the interval=yearly) parameter. [block:code] { "codes": [ { "code": "curl --header \"apikey: [[app:key]]\" \"https://api.triathlon.org/v1/statistics/results?group_by=position&filters=athlete.id,eq,7788|position,lte,3&timeframe=this_7_years&interval=yearly\"", "language": "curl" } ] } [/block] [block:callout] { "type": "warning", "title": "Intervals require a timeframe to be set", "body": "You must set a timeframe to use intervals and the API will return a 400 bad request response if it not set." } [/block]