Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/frictionlessdata/tableschema-r

An R library for working with Table Schema.
https://github.com/frictionlessdata/tableschema-r

Last synced: 3 months ago
JSON representation

An R library for working with Table Schema.

Awesome Lists containing this project

README

        

---
title:



rictionless Data -
Table Schema
output:
github_document:
html_preview: no
number_sections: yes
---
[![CRAN_Status_Badge](https://www.r-pkg.org/badges/version/tableschema.r)](https://cran.r-project.org/package=tableschema.r)
[![R-CMD-check](https://github.com/frictionlessdata/tableschema-r/workflows/R-CMD-check/badge.svg)](https://github.com/frictionlessdata/tableschema-r/actions)
[![Coverage status](https://coveralls.io/repos/github/frictionlessdata/tableschema-r/badge.svg)](https://coveralls.io/r/frictionlessdata/tableschema-r?branch=master)
[![Project Status: Active – The project has reached a stable, usable state and is being actively developed.](http://www.repostatus.org/badges/latest/active.svg)](http://www.repostatus.org/#active)
[![](http://cranlogs.r-pkg.org/badges/grand-total/tableschema.r)](http://cran.rstudio.com/web/packages/tableschema.r/index.html)
[![Licence](https://img.shields.io/badge/licence-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![Support](https://img.shields.io/badge/support-discord-brightgreen)](https://discordapp.com/invite/Sewv6av)

# Description

R library for working with [Table Schema][tableschema].

# Features

+ `Table` class for working with data and schema
+ `Schema` class for working with schemas
+ `Field` class for working with schema fields
+ `validate` function for validating schema descriptors
+ `infer` function that creates a schema based on a data sample

# Getting started

## Installation

In order to install the latest distribution of [R software][Rs] to your computer you have to select one of the mirror sites of the [Comprehensive R Archive Network][R], select the appropriate link for your operating system and follow the wizard instructions.

For windows users you can:

1. Go to CRAN
2. Click download R for Windows
3. Click Base (This is what you want to install R for the first time)
4. Download the latest R version
5. Run installation file and follow the instrustions of the installer.

(Mac) OS X and Linux users may need to follow different steps depending on their system version to install R successfully and it is recommended to read the instructions on CRAN site carefully.

Even more detailed installation instructions can be found in [R Installation and Administration manual][Rman].

To install [RStudio][Rstudio], you can download [RStudio Desktop][Rstudiodown] with Open Source License and follow the wizard instructions:

1. Go to [RStudio](https://www.rstudio.com/products/rstudio/)
2. Click download on RStudio Desktop
3. Download on RStudio Desktop free download
4. Select the appropriate file for your system
5. Run installation file

To install the `tableschema` library it is necessary to install first `devtools` library to make installation of github libraries available.

```{r, eval=FALSE, include=T}
# Install devtools package if not already
install.packages("devtools")
```

Install `tableschema.r`

```{r, eval=FALSE, include=T}
# from CRAN version
install.packages("tableschema.r")

# or install the development version from github
devtools::install_github("frictionlessdata/tableschema-r")
```

## Load library
```{r echo=TRUE, message=FALSE, warning=FALSE}
# Install devtools package if not already
# install.packages("jsonlite")
library(jsonlite)
# Install devtools package if not already
# install.packages("future")
library(future)
# load the library using
library(tableschema.r)
```

# Documentation

[Jsonlite package](https://CRAN.R-project.org/package=jsonlite) is internally used to convert json data to list objects. The input parameters of functions could be json strings, files or lists and the outputs are in list format to easily further process your data in R environment and exported as desired. The examples below show how to use jsonlite package to convert the output back to json adding indentation whitespace. More details about handling json you can see jsonlite documentation or vignettes [here](https://CRAN.R-project.org/package=jsonlite).

Moreover [future package](https://CRAN.R-project.org/package=future) is also used to load and create Table and Schema classes asynchronously. To retrieve the actual result of the loaded Table or Schema you have to use `value(...)` to the variable you stored the loaded Table/Schema. More details about future package and sequential and parallel processing you can find [here](https://CRAN.R-project.org/package=future).

## Working with Table

A table is a core concept in a tabular data world. It represents a data with a metadata (Table Schema). Let's see how we could use it in practice.

Consider we have some local csv file. It could be inline data or remote link - all supported by `Table` class (except local files for in-brower usage of course). But say it's `data.csv` for now:

> data/cities.csv

```csv
city,location
london,"51.50,-0.11"
paris,"48.85,2.30"
rome,N/A
```

Let's create and read a table. We use static `Table.load` method and `table.read` method with a `keyed` option to get list of keyed rows:

```{r eval=TRUE, message=FALSE, warning=FALSE, include=TRUE, paged.print=TRUE}
def = Table.load('inst/extdata/data.csv')
table = value(def)
# add indentation whitespace to JSON output with jsonlite package
toJSON(table$read(keyed = TRUE), pretty = TRUE) # function from jsonlite package
```
```{r eval=TRUE, message=FALSE, warning=FALSE, include=TRUE, paged.print=TRUE}
table.headers = table$headers
table.headers
```

As we could see our locations are just a strings. But it should be geopoints. Also Rome's location is not available but it's also just a `N/A` string instead of `null`. First we have to infer Table Schema:
```{r eval=TRUE, message=FALSE, warning=FALSE, include=TRUE}
# add indentation whitespace to JSON output with jsonlite package
toJSON(table$infer(), pretty = TRUE) # function from jsonlite package
```

```{r, eval=TRUE, include=TRUE}
toJSON(table$schema$descriptor, pretty = TRUE) # function from jsonlite package
```

```{r eval=FALSE, message=TRUE,error=TRUE, warning=FALSE, include=TRUE}
table$read(keyed = TRUE) # Fails
```

Let's fix not available location. There is a `missingValues` property in Table Schema specification. As a first try we set `missingValues` to `N/A` in `table$schema$descriptor`. Schema descriptor could be changed in-place but all changes should be commited by `table$schema$commit()`:

```{r, eval = TRUE, include = TRUE, warning=FALSE}
table$schema$descriptor['missingValues'] = 'N/A'
table$schema$commit()
```

```{r, eval = TRUE, include = TRUE, warning=FALSE}
table$schema$valid # false
```
```{r, eval = TRUE, include = TRUE, warning=FALSE}
table$schema$errors
```

As a good citiziens we've decided to check out schema descriptor validity. And it's not valid! We sould use an list for `missingValues` property. Also don't forget to have an empty string as a missing value:

```{r, eval=TRUE, include=TRUE}
table$schema$descriptor[['missingValues']] = list("", 'N/A')
table$schema$commit()
table$schema$valid # true
```

All good. It looks like we're ready to read our data again:

```{r, eval=FALSE, include=TRUE}
table$read() # or
```
```{r, eval=FALSE, include=TRUE}
toJSON(table$read(), pretty = TRUE) # function from jsonlite package
```

Now we see that:

+ locations are lists with numeric lattide and longitude

+ Rome's location is `null`

And because there are no errors on data reading we could be sure that our data is valid againt our schema. Let's save it:

```{r, eval=FALSE, include=TRUE}
table$schema$save('schema.json')
table$save('data.csv')
```

Our `data.csv` looks the same because it has been stringified back to `csv` format. But now we have `schema.json`:

```json
{
"fields": [
{
"name": "city",
"type": "string",
"format": "default"
},
{
"name": "location",
"type": "geopoint",
"format": "default"
}
],
"missingValues": [
"",
"N/A"
]
}

```

If we decide to improve it even more we could update the schema file and then open it again. But now providing a schema path.

```{r, eval=TRUE, include=TRUE}
def = Table.load('inst/extdata/data.csv', schema = 'inst/extdata/schema.json')
table = value(def)
table
```

It was only basic introduction to the `Table` class. To learn more let's take a look on `Table` class API reference.

## Working with Schema

A model of a schema with helpful methods for working with the schema and supported data. Schema instances can be initialized with a schema source as a url to a JSON file or a JSON object. The schema is initially validated (see [validate](#validate) below). By default validation errors will be stored in `schema$errors` but in a strict mode it will be instantly raised.

Let's create a blank schema. It's not valid because `descriptor$fields` property is required by the [Table Schema](https://specs.frictionlessdata.io/table-schema/) specification:

```{r, eval=TRUE, include=TRUE}
def = Schema.load({})
schema = value(def)
schema$valid # false
schema$errors
```

To do not create a schema descriptor by hands we will use a `schema$infer` method to infer the descriptor from given data:

```{r, eval=TRUE, include=TRUE}
toJSON(
schema$infer(helpers.from.json.to.list('[
["id", "age", "name"],
["1","39","Paul"],
["2","23","Jimmy"],
["3","36","Jane"],
["4","28","Judy"]
]')), pretty = TRUE) # function from jsonlite package
```

```{r, eval=TRUE, include=TRUE}
schema$valid # true
```

```{r, eval=TRUE, include=TRUE}
toJSON(
schema$descriptor,
pretty = TRUE) # function from jsonlite package
```

Now we have an inferred schema and it's valid. We could cast data row against our schema. We provide a string input by an output will be cast correspondingly:

```{r, eval=TRUE, include=TRUE}
toJSON(
schema$castRow(helpers.from.json.to.list('["5", "66", "Sam"]')),
pretty = TRUE, auto_unbox = TRUE) # function from jsonlite package
```

But if we try provide some missing value to `age` field cast will fail because for now only one possible missing value is an empty string. Let's update our schema:

```{r, eval=TRUE,error= TRUE, include=TRUE}
schema$castRow(helpers.from.json.to.list('["6", "N/A", "Walt"]'))
# Cast error
```

```{r, eval=TRUE, include=TRUE}
schema$descriptor$missingValues = list('', 'NA')
schema$commit()
```

```{r, eval=TRUE, include=TRUE}
schema$castRow(helpers.from.json.to.list('["6", "", "Walt"]'))
```

We could save the schema to a local file. And we could continue the work in any time just loading it from the local file:

```{r, eval=FALSE, include=TRUE}
schema$save('schema.json')
schema = Schema.load('schema.json')
```

It was only basic introduction to the `Schema` class. To learn more let's take a look on `Schema` class API reference.

## Working with Field

Class represents field in the schema.

Data values can be cast to native R types. Casting a value will check the value is of the expected type, is in the correct format, and complies with any constraints imposed by a schema.

```json
{
"name": "birthday",
"type": "date",
"format": "default",
"constraints": {
"required": true,
"minimum": "2015-05-30"
}
}
```

Following code will not raise the exception, despite the fact our date is less than minimum constraints in the field, because we do not check constraints of the field descriptor

```{r, eval=TRUE, include=TRUE}
field = Field$new(helpers.from.json.to.list('{"name": "name", "type": "number"}'))
dateType = field$cast_value('12345') # cast
dateType # print the result
```

And following example will raise exception, because we set flag 'skip constraints' to `false`, and our date is less than allowed by `minimum` constraints of the field. Exception will be raised as well in situation of trying to cast non-date format values, or empty values

```{r, eval=TRUE, error=TRUE, include=TRUE}
tryCatch(
dateType = field$cast_value(value = '2014-05-29', constraints = FALSE),
error = function(e){# uh oh, something went wrong
})
```

Values that can't be cast will raise an `Error` exception. Casting a value that doesn't meet the constraints will raise an `Error` exception.

Table below shows the available types, formats and resultant value of the cast:

+-----------+-----------------------------+------------------+
| Type | Formats | Casting result |
+:==========+:============================+:=================+
| any | default | Any |
+-----------+-----------------------------+------------------+
| list | default | List |
+-----------+-----------------------------+------------------+
| boolean | default | Boolean |
+-----------+-----------------------------+------------------+
| date | default, any | Date |
+-----------+-----------------------------+------------------+
| datetime | default, any | Date |
+-----------+-----------------------------+------------------+
| duration | default | Duration |
+-----------+-----------------------------+------------------+
| geojson | default, topojson | Object |
+-----------+-----------------------------+------------------+
| geopoint | default, list, object | [Number, Number] |
+-----------+-----------------------------+------------------+
| integer | default | Number |
+-----------+-----------------------------+------------------+
| number | default | Number |
+-----------+-----------------------------+------------------+
| object | default | Object |
+-----------+-----------------------------+------------------+
| string | default, uri, email, binary | String |
+-----------+-----------------------------+------------------+
| time | default, any | Date |
+-----------+-----------------------------+------------------+
| year | default | Number |
+-----------+-----------------------------+------------------+
| yearmonth | default | [Number, Number] |
+-----------+-----------------------------+------------------+

### Working with Validate

> `validate()` validates whether a **schema** is a validate Table Schema accordingly to the [specifications](https://specs.frictionlessdata.io/schemas/table-schema.json). It does **not** validate data against a schema.

Given a schema descriptor `validate` returns a validation object:

```{r, eval=TRUE, include=TRUE}
valid_errors = validate('inst/extdata/schema.json')
valid_errors
```

### Working with Infer

Given data source and headers `infer` will return a Table Schema as a JSON object based on the data values.

Given the data file, example.csv:

```csv
id,age,name
1,39,Paul
2,23,Jimmy
3,36,Jane
4,28,Judy
```

Call `infer` with headers and values from the datafile:

```{r, eval=TRUE, include=TRUE}
descriptor = infer('inst/extdata/data_infer.csv')
```

The `descriptor` variable is now a list object that can easily converted to JSON:

```{r, eval=TRUE, include=TRUE}
toJSON(
descriptor,
pretty = TRUE
) # function from jsonlite package
```

## API Reference

### Table
Table representation

* [Table](#Table)
* _instance_
* [$headers](#Table+headers) ⇒ List.<string>
* [$schema](#Table+schema) ⇒ Schema
* [$iter(keyed, extended, cast, forceCast, relations, stream)](#Table+iter) ⇒ AsyncIterator \| Stream
* [$read(limit)](#Table+read) ⇒ List.<List> \| List.<Object>
* [$infer(limit)](#Table+infer) ⇒ Object
* [$save(target)](#Table+save) ⇒ Boolean
* _static_
* [.load(source, schema, strict, headers, parserOptions)](#Table.load) ⇒ [Table](#Table)

#### table$headers ⇒ List.<string>
Headers

**Returns**: List.<string> - data source headers

#### table$schema ⇒ Schema
Schema

**Returns**: Schema - table schema instance

#### table$iter(keyed, extended, cast, forceCast, relations, stream) ⇒ AsyncIterator \| Stream
Iterate through the table data

And emits rows cast based on table schema (async for loop).
With a `stream` flag instead of async iterator a Node stream will be returned.
Data casting can be disabled.

**Returns**: AsyncIterator \| Stream - async iterator/stream of rows:
- `[value1, value2]` - base
- `{header1: value1, header2: value2}` - keyed
- `[rowNumber, [header1, header2], [value1, value2]]` - extended
**Throws**:

- TableSchemaError raises any error occurred in this process

| Param | Type | Description |
| --- | --- | --- |
| keyed | boolean | iter keyed rows |
| extended | boolean | iter extended rows |
| cast | boolean | disable data casting if false |
| forceCast | boolean | instead of raising on the first row with cast error return an error object to replace failed row. It will allow to iterate over the whole data file even if it's not compliant to the schema. Example of output stream: `[['val1', 'val2'], TableSchemaError, ['val3', 'val4'], ...]` |
| relations | Object | object of foreign key references in a form of `{resource1: [{field1: value1, field2: value2}, ...], ...}`. If provided foreign key fields will checked and resolved to its references |
| stream | boolean | return Node Readable Stream of table rows |

#### table$read(limit) ⇒ List.<List> \| List.<Object>
Read the table data into memory

> The API is the same as `table.iter` has except for:

**Returns**: List.<List> \| List.<Object> - list of rows:
- `[value1, value2]` - base
- `{header1: value1, header2: value2}` - keyed
- `[rowNumber, [header1, header2], [value1, value2]]` - extended

| Param | Type | Description |
| --- | --- | --- |
| limit | integer | limit of rows to read |

#### table$infer(limit) ⇒ Object
Infer a schema for the table.

It will infer and set Table Schema to `table.schema` based on table data.

**Returns**: Object - Table Schema descriptor

| Param | Type | Description |
| --- | --- | --- |
| limit | number | limit rows sample size |

#### table$save(target) ⇒ Boolean
Save data source to file locally in CSV format with `,` (comma) delimiter

**Returns**: Boolean - true on success
**Throws**:

- TableSchemaError an error if there is saving problem

| Param | Type | Description |
| --- | --- | --- |
| target | string | path where to save a table data |

#### Table.load(source, schema, strict, headers, parserOptions) ⇒ [Table](#Table)
Factory method to instantiate `Table` class.

This method is async and it should be used with await keyword or as a `Promise`.
If `references` argument is provided foreign keys will be checked
on any reading operation.

**Returns**: [Table](#Table) - data table class instance
**Throws**:

- TableSchemaError raises any error occurred in table creation process

| Param | Type | Description |
| --- | --- | --- |
| source | string \| List.<List> \| Stream \| function | data source (one of): - local CSV file (path) - remote CSV file (url) - list of lists representing the rows - readable stream with CSV file contents - function returning readable stream with CSV file contents |
| schema | string \| Object | data schema in all forms supported by `Schema` class |
| strict | boolean | strictness option to pass to `Schema` constructor |
| headers | number \| List.<string> | data source headers (one of): - row number containing headers (`source` should contain headers rows) - list of headers (`source` should NOT contain headers rows) |
| parserOptions | Object | options to be used by CSV parser. All options listed at . By default `ltrim` is true according to the CSV Dialect spec. |

### Schema
Schema representation

* [Schema](#Schema)
* _instance_
* [$valid](#Schema+valid) ⇒ Boolean
* [$errors](#Schema+errors) ⇒ List.<Error>
* [$descriptor](#Schema+descriptor) ⇒ Object
* [$primaryKey](#Schema+primaryKey) ⇒ List.<string>
* [$foreignKeys](#Schema+foreignKeys) ⇒ List.<Object>
* [$fields](#Schema+fields) ⇒ List.<Field>
* [$fieldNames](#Schema+fieldNames) ⇒ List.<string>
* [$getField(fieldName)](#Schema+getField) ⇒ Field \| null
* [$addField(descriptor)](#Schema+addField) ⇒ Field
* [$removeField(name)](#Schema+removeField) ⇒ Field \| null
* [$castRow(row, failFalst)](#Schema+castRow) ⇒ List.<List>
* [$infer(rows, headers)](#Schema+infer) ⇒ Object
* [$commit(strict)](#Schema+commit) ⇒ Boolean
* [$save(target)](#Schema+save) ⇒ boolean
* _static_
* [.load(descriptor, strict)](#Schema.load) ⇒ [Schema](#Schema)

#### schema$valid ⇒ Boolean
Validation status

It always `true` in strict mode.

**Returns**: Boolean - returns validation status

#### schema$errors ⇒ List.<Error>
Validation errors

It always empty in strict mode.

**Returns**: List.<Error> - returns validation errors

#### schema$descriptor ⇒ Object
Descriptor

**Returns**: Object - schema descriptor

#### schema$primaryKey ⇒ List.<string>
Primary Key

**Returns**: List.<string> - schema primary key

#### schema$foreignKeys ⇒ List.<Object>
Foreign Keys

**Returns**: List.<Object> - schema foreign keys

#### schema$fields ⇒ List.<Field>
Fields

**Returns**: List.<Field> - schema fields

#### schema$fieldNames ⇒ List.<string>
Field names

**Returns**: List.<string> - schema field names

#### schema$getField(fieldName) ⇒ Field \| null
Return a field

**Returns**: Field \| null - field instance if exists

| Param | Type |
| --- | --- |
| fieldName | string |

#### schema$addField(descriptor) ⇒ Field
Add a field

**Returns**: Field - added field instance

| Param | Type |
| --- | --- |
| descriptor | Object |

#### schema$removeField(name) ⇒ Field \| null
Remove a field

**Returns**: Field \| null - removed field instance if exists

| Param | Type |
| --- | --- |
| name | string |

#### schema$castRow(row, failFalst) ⇒ List.<List>
Cast row based on field types and formats.

**Returns**: List.<List> - cast data row

| Param | Type | Description |
| --- | --- | --- |
| row | List.<List> | data row as an list of values |
| failFalst | boolean | |

#### schema$infer(rows, headers) ⇒ Object
Infer and set `schema.descriptor` based on data sample.

**Returns**: Object - Table Schema descriptor

| Param | Type | Description |
| --- | --- | --- |
| rows | List.<List> | list of lists representing rows |
| headers | integer \| List.<string> | data sample headers (one of): - row number containing headers (`rows` should contain headers rows) - list of headers (`rows` should NOT contain headers rows) - defaults to 1 |

#### schema$commit(strict) ⇒ Boolean
Update schema instance if there are in-place changes in the descriptor.

**Returns**: Boolean - returns true on success and false if not modified
**Throws**:

- TableSchemaError raises any error occurred in the process

| Param | Type | Description |
| --- | --- | --- |
| strict | boolean | alter `strict` mode for further work |

**Example**
```{r, eval=TRUE, include=TRUE}
descriptor <- '{"fields": [{"name": "field", "type": "string"}]}'
def <- Schema.load(descriptor)
schema <- value(def)
schema$getField('name')
schema$descriptor$fields[[1]]$type
schema$descriptor$fields[[1]]$type <-'number'
schema$descriptor$fields[[1]]$type
schema$commit()
```

#### schema$save(target) ⇒ boolean
Save schema descriptor to target destination.

**Returns**: boolean - returns true on success
**Throws**:

- TableSchemaError raises any error occurred in the process

| Param | Type | Description |
| --- | --- | --- |
| target | string | path where to save a descriptor |

#### Schema.load(descriptor, strict) ⇒ [Schema](#Schema)
Factory method to instantiate `Schema` class.

This method is async and it should be used with await keyword or as a `Promise`.

**Returns**: [Schema](#Schema) - returns schema class instance
**Throws**:

- TableSchemaError raises any error occurred in the process

| Param | Type | Description |
| --- | --- | --- |
| descriptor | string \| Object | schema descriptor: - local path - remote url - object |
| strict | boolean | flag to alter validation behaviour: - if false error will not be raised and all error will be collected in `schema.errors` - if strict is true any validation error will be raised immediately |

### Field
Field representation

* [Field](#Field)
* [new Field(descriptor, missingValues)](#new_Field_new)
* [$name](#Field+name) ⇒ string
* [$type](#Field+type) ⇒ string
* [$format](#Field+format) ⇒ string
* [$required](#Field+required) ⇒ boolean
* [$constraints](#Field+constraints) ⇒ Object
* [$descriptor](#Field+descriptor) ⇒ Object
* [$castValue(value, constraints)](#Field+castValue) ⇒ any
* [$testValue(value, constraints)](#Field+testValue) ⇒ boolean

#### new Field(descriptor, missingValues)
Constructor to instantiate `Field` class.

**Returns**: [Field](#Field) - returns field class instance
**Throws**:

- TableSchemaError raises any error occured in the process

| Param | Type | Description |
| --- | --- | --- |
| descriptor | Object | schema field descriptor |
| missingValues | List.<string> | an list with string representing missing values |

#### field$name ⇒ string
Field name

#### field$type ⇒ string
Field type

#### field$format ⇒ string
Field format

#### field$required ⇒ boolean
Return true if field is required

#### field$constraints ⇒ Object
Field constraints

#### field$descriptor ⇒ Object
Field descriptor

#### field$castValue(value, constraints) ⇒ any
Cast value

**Returns**: any - cast value

| Param | Type | Description |
| --- | --- | --- |
| value | any | value to cast |
| constraints | Object \| false | |

#### field$testValue(value, constraints) ⇒ boolean
Check if value can be cast

| Param | Type | Description |
| --- | --- | --- |
| value | any | value to test |
| constraints | Object \| false | |

### validate(descriptor) ⇒ Object
This function is async so it has to be used with `await` keyword or as a `Promise`.

**Returns**: Object - returns `{valid, errors}` object

| Param | Type | Description |
| --- | --- | --- |
| descriptor | string \| Object | schema descriptor (one of): - local path - remote url - object |

### infer(source, headers, options) ⇒ Object
This function is async so it has to be used with `await` keyword or as a `Promise`.

**Returns**: Object - returns schema descriptor
**Throws**:

- TableSchemaError raises any error occured in the process

| Param | Type | Description |
| --- | --- | --- |
| source | string \| List.<List> \| Stream \| function | data source (one of): - local CSV file (path) - remote CSV file (url) - list of lists representing the rows - readable stream with CSV file contents - function returning readable stream with CSV file contents |
| headers | List.<string> | list of headers |
| options | Object | any `Table.load` options |

# Contributing
The project follows the [Open Knowledge International coding standards][coding_standards]. There are common commands to work with the project.Recommended way to get started is to create, activate and load the library environment. To install package and development dependencies into active environment:

```{r, eval=FALSE, include=TRUE}
devtools::install_github("frictionlessdata/tableschema-r", dependencies = TRUE)
```

To make test:
```{r, eval=FALSE, include=TRUE}
test_that(description, {
expect_equal(test, expected result)
})
```

To run tests:
```{r, eval=FALSE, include=TRUE}
devtools::test()
```
More detailed information about how to create and run tests you can find in [testthat package](https://github.com/r-lib/testthat).

## Changelog - News

In [NEWS.md][news] described only breaking and the most important changes. The full changelog could be found in nicely formatted [commit][commits] history.

[Rs]: https://www.r-project.org/
[R]: https://cran.r-project.org/
[Rman]: https://cran.r-project.org/doc/manuals/R-admin.html
[Rstudio]: https://www.rstudio.com/
[Rstudiodown]: https://www.rstudio.com/products/rstudio/download/
[coding_standards]: https://github.com/okfn/coding-standards
[tableschema]: https://specs.frictionlessdata.io/table-schema/
[news]: https://github.com/frictionlessdata/tableschema-r/blob/master/NEWS.md
[commits]: https://github.com/frictionlessdata/tableschema-r/commits/master