# Introduction to the New MATLAB Data Types in R2013b 30

Posted by **Loren Shure**,

Today I’d like to introduce a fairly frequent guest blogger Sarah Wait Zaranek who works for the MATLAB Marketing team here at The MathWorks. She and I will be writing about the new capabilities for MATLAB in R2013b. In particular, there are two new data types in MATLAB in R2013b – table and categorical arrays.

### Contents

- What are Tables and Categorical Arrays?
- Importing Data into a Table
- Looking at Variable Names (Column Names)
- Accessing the Data in Your Table
- Converting Data to Categorical Arrays
- Creating a New Table
- Adding/Removing Variables
- Removing Missing Data
- Summarizing a Table
- Sorting Data
- Applying Functions to Table Variables
- Joining (Merging) Tables
- Plotting Data From the Final Table
- Your Thoughts?

#### What are Tables and Categorical Arrays?

Table is a new data type suitable for holding heterogenous data and metadata. Specifically, tables are useful for mixed-type tabular data that are often stored as columns in a text file or in a spreadsheet. Tables consist of rows and column-oriented variables. Categorical arrays are useful for holding categorical data - which have values from a finite list of discrete categories.

One of the best ways to learn more about tables and categorical arrays is to see them in action. So, in this post, we will use tables and categoricals to examine some airplane flight delay data. The flight data is freely available from the Bureau of Transportation Statistics (BTS). You can download it yourself here. The weather data is from the National Climatic Data Center (NCDC) and is available here.

#### Importing Data into a Table

You can import your data into a table interactively using the Import Tool or you can do it programmatically, using `readtable`.

FlightData = readtable('Jan2010Flights.csv'); whos FlightData

Name Size Bytes Class Attributes FlightData 17816x7 9007742 table

The entire contents of the file are now contained in a single variable – a table. Here you are reading in your data from a csv file. `readtable` also supports reading from .txt,.dat text files and Excel spreadsheet files. Tables can also be created directly from variables in your workspace.

#### Looking at Variable Names (Column Names)

You can see all the variable names (column names in our table) by looking at the `VariableNames` properties.

FlightData.Properties.VariableNames

ans = Columns 1 through 5 'FL_DATE' 'CARRIER' 'ORIGIN' 'DEST' 'CRS_DEP_TIME' Columns 6 through 7 'DEP_TIME' 'DEP_DELAY'

This particular table does not contain any row names, but for a table with row names you can access the row names using the `RowNames` property.

#### Accessing the Data in Your Table

There are multiple ways to access the data in your table. You can use dot indexing to access or modify a single table variable, similar to how you use fieldnames in structures.

For example, using dot indexing you can plot a histogram of the departure delays (in minutes).

```
hist(FlightData.DEP_DELAY)
title('Histogram of Flight Delays in Minutes')
```

You can also display the first 5 departure delays.

FlightData.DEP_DELAY(1:5)

ans = 49 -7 -5 -8 -10

You can also extract data from one or more variables in the table using curly braces. Within the curly braces you can use numeric indexing or variable and row names. For example, you can extract the actual departure times and scheduled depature times for the first 5 flights.

SomeTimes = FlightData{1:5,{'DEP_TIME','CRS_DEP_TIME'}}; disp(SomeTimes)

1149 1100 1053 1100 1055 1100 1052 1100 1050 1100

This is similar to indexing with cell arrays. However, **unlike** with cells, this concatenates the specified variables into a single array. Therefore, the data types of all the specified variables need to be compatible for concatenation.

#### Converting Data to Categorical Arrays

You can convert some of the variables in your table using `categorical`. Categorical arrays are more memory efficent than holding cell arrays of strings when you have repeated data. Categorical arrays store only one copy of each category name, reducing the amount of memory required to store the array. You can use `whos` to see the amount of memory you save by converting the data to a categorical array.

```
whos FlightData
```

Name Size Bytes Class Attributes FlightData 17816x7 9007742 table

```
FlightData.ORIGIN = categorical(FlightData.ORIGIN);
FlightData.DEST = categorical(FlightData.DEST);
FlightData.CARRIER = categorical(FlightData.CARRIER);
whos FlightData
```

Name Size Bytes Class Attributes FlightData 17816x7 2857264 table

Using `categories`, you can find all the distinct categories in your array. By default, categorical arrays do not define a definite order. If your data contains categories with a definite order, you can set the `'Ordinal'` flag to `true` when creating your categorical array. The default the order will be alphabetical, but you can prescribe you own order instead.

categories(FlightData.CARRIER)

ans = '9E' 'AA' 'AS' 'B6' 'CO' 'DL' 'F9' 'FL' 'MQ' 'OH' 'UA' 'US' 'WN' 'XE' 'YV'

Categorical arrays are also faster and more convenient than cell arrays of strings for indexing and searching. By converting to categorical arrays, you can then mathematically compare sets of strings just like you would do with numeric values. You can use this functionality to create a new table containing only the flights that left from Boston.

#### Creating a New Table

You can create a new table from a section of an existing table using parentheses with numerical indexing, variable names, or row names. Since the flight origin is now a categorical array, you can use logical indexing to find all flights that left from Boston.

```
idxBoston = FlightData.ORIGIN == 'BOS' ;
BostonFlights = FlightData(idxBoston,:);
height(FlightData)
height(BostonFlights)
```

ans = 17816 ans = 8904

#### Adding/Removing Variables

You can also modify your table by adding and removing variables and rows. All variables in a table must have the same number of rows, but they can be of different widths.

Let's add a new variable (DATE) to represent the serial date number for the various flight dates.

BostonFlights.DATE = datenum(BostonFlights.FL_DATE);

The origin now has all `BOS` values and you are not going to use destination information right now, so those variables can be removed. `HOUR` can also be calculated, as well as a `LATE` variable which indicates if the flight was 15 minutes late or more.

```
BostonFlights.ORIGIN = [];
BostonFlights.DEST = [];
BostonFlights.FL_DATE = [];
BostonFlights.HOUR = floor(BostonFlights.CRS_DEP_TIME./100);
BostonFlights{:,'LATE'} = BostonFlights.DEP_DELAY > 15;
```

#### Removing Missing Data

Tables have supported functions for finding and standardizing missing data. In this case, you can find any missing data using `ismissing` and remove it. You can use `height`, which gives you the number of table rows, to see how many flights were removed from the table. We exploit logical indexing to get only the flights that have no missing data.

height(BostonFlights)

ans = 8904

TF = any(ismissing(BostonFlights),2); BostonFlights = BostonFlights(~TF,:); height(BostonFlights)

ans = 8640

#### Summarizing a Table

You can then see descriptive statistics for each variable in this new table by using `summary`.

summary(BostonFlights)

Variables: CARRIER: 8640x1 categorical Values: 9E 85 AA 913 AS 55 B6 1726 CO 321 DL 1215 F9 26 FL 536 MQ 751 OH 341 UA 643 US 1494 WN 384 XE 93 YV 57 CRS_DEP_TIME: 8640x1 double Values: min 500 median 1215 max 2359 DEP_TIME: 8640x1 double Values: min 2 median 1224 max 2400 DEP_DELAY: 8640x1 double Values: min -25 median -3 max 419 DATE: 8640x1 double Values: min 7.3414e+05 median 7.3415e+05 max 7.3417e+05 HOUR: 8640x1 double Values: min 5 median 12 max 23 LATE: 8640x1 logical Values: true 1471 false 7169

#### Sorting Data

There are additional functions to sort tables, apply functions to table variables, and merge tables together. For example, you can sort your `BostonFlights` by departure delay.

BostonFlights = sortrows(BostonFlights,'DEP_DELAY','descend'); BostonFlights(1:10,:)

ans = CARRIER CRS_DEP_TIME DEP_TIME DEP_DELAY DATE HOUR _______ ____________ ________ _________ __________ ____ DL 1830 129 419 7.3416e+05 18 DL 1850 111 381 7.3414e+05 18 CO 1755 10 375 7.3414e+05 17 AA 1710 2323 373 7.3416e+05 17 DL 630 1240 370 7.3416e+05 6 FL 1400 1951 351 7.3414e+05 14 FL 1741 2330 349 7.3414e+05 17 UA 1906 22 316 7.3414e+05 19 AA 840 1355 315 7.3416e+05 8 AA 905 1420 315 7.3414e+05 9 LATE _____ true true true true true true true true true true

#### Applying Functions to Table Variables

You can apply functions to work with table variables, with `varfun`.

`varfun` has optional additional calling inputs such as `'InputVariables'` and `'GroupingVariables'`. `'InputVariables'` lets you specific which variables you want to operate on instead of operating on all the variables in your table. `'GroupingVariables'` let you define groups of rows on which to operate. `varfun` would then apply your function to each group of rows within each of the variables of your table, rather than to each entire variable.

You can use `varfun` to calculate the mean delay for all flights and the fraction of late flights for a given hour on a given day. The default output of `varfun` is a table.

ByHour = varfun(@mean, BostonFlights, ... 'InputVariables', {'DEP_DELAY', 'LATE'},... 'GroupingVariables',{'DATE','HOUR'}); disp(ByHour(1:5,:))

DATE HOUR GroupCount mean_DEP_DELAY mean_LATE __________ ____ __________ ______________ _________ 734139_5 7.3414e+05 5 5 3.8 0 734139_6 7.3414e+05 6 19 10.579 0.31579 734139_7 7.3414e+05 7 18 6.7778 0.11111 734139_8 7.3414e+05 8 21 8.8571 0.33333 734139_9 7.3414e+05 9 17 5.0588 0.23529

#### Joining (Merging) Tables

Weather might have an important role in determining if a flight is delayed. For a given hour, you might want to know both the delayed flight information and the weather at the airport. So, you can start by reading in another table containing weather data for Boston Logan Airport. Then, you can merge that table with the existing `ByHour` table.

Since there are a lot of variables in this file, you can specify the input format when using `readtable`. This allows you to use `*` to skip variables that you aren't interested in loading into the table. For more information about specifying formating strings, see here in the documentation. Since this data uses 'M' to represent missing data, you can use `'TreatAsEmpty'` to replace any instances of 'M' with the standard missing value indicator (NaN for numeric values).

FormatStr = ['%*s%s%f' repmat('%*s',1,9) '%f' repmat('%*s',1,7) '%f',... repmat('%*s',1,3),'%f', repmat('%*s',1,18)]; WeatherData = readtable('BostonWeather.txt','HeaderLines',6,... 'Format',FormatStr,'TreatAsEmpty','M'); WeatherData.Properties.VariableNames

ans = 'Date' 'Time' 'DryBulbCelsius' 'DewPointCelsius' 'WindSpeed'

WeatherData contains the date, time, dew point and dry bulb temperature in Celsius, and wind speed. Let's convert `DATE` to a serial date number and round to the hour for the time measurement.

```
WeatherData.DATE = datenum(WeatherData.Date,'yyyymmdd');
WeatherData.Date = [];
WeatherData.HOUR = floor(WeatherData.Time/100);
```

Since there are multiple weather measurements per hour, you can average the data by hour using `varfun`.

ByHourWeather = varfun(@mean, WeatherData, ... 'InputVariables', {'DryBulbCelsius','DewPointCelsius','WindSpeed'},... 'GroupingVariables',{'DATE','HOUR'});

Now, you can merge the two tables using `join` which matches rows using key variables (columns) common to both tables. `join` keeps all the variables from the first input table and appends the corresponding variables from the second input table. The table that `join` creates will use the key variable values as the row names. In this case, that means the row names will represent the date and hour data.

AllData = join(ByHour,ByHourWeather,'Keys',{'DATE','HOUR'});

#### Plotting Data From the Final Table

Let's now plot this final data set to get an idea of the effect of weather on the flight delays.

AllData.TDIFF = ... abs(AllData.mean_DewPointCelsius - AllData.mean_DryBulbCelsius); scatter(AllData.TDIFF, AllData.mean_DEP_DELAY,... [], AllData.mean_DEP_DELAY,'filled'); xlabel('abs(DewPoint-Temperature)') ylabel('Average Departure Delay')

scatter3( AllData.HOUR,AllData.TDIFF,AllData.mean_DEP_DELAY,... [],AllData.mean_DEP_DELAY,'filled'); xlabel('Hour of Flight') ylabel('abs(DewPoint-Temperature)') zlabel('Average Departure Delay')

Qualitatively it looks having a temperature near the dew point (greater change of precipitation) effects the departure time. There are other factors at work as well, but it is nice to know that our intuition (flights later in the day and when it is cold and snowing might have a greater chance to be delayed) seems to work with the data.

#### Your Thoughts?

Can you see yourself using tables and categorical arrays? Let us know what you think or if you have any questions by leaving a comment here.

Get the MATLAB code

Published with MATLAB® R2013b

**Category:**- Data types,
- New Feature

### Note

Comments are closed.

## 30 CommentsOldest to Newest

**1**of 30

**2**of 30

**3**of 30

**4**of 30

**5**of 30

**6**of 30

**7**of 30

**8**of 30

**9**of 30

**10**of 30

**11**of 30

**12**of 30

**13**of 30

**14**of 30

**15**of 30

**16**of 30

**17**of 30

**18**of 30

**19**of 30

**20**of 30

**21**of 30

**22**of 30

**23**of 30

**24**of 30

**25**of 30

**26**of 30

>> t = array2table(randn(5,3),’VariableNames’,{‘x’ ‘y’ ‘z’}) t = x y z _______ ________ _________ 0.53767 -1.3077 -1.3499 1.8339 -0.43359 3.0349 -2.2588 0.34262 0.7254 0.86217 3.5784 -0.063055 0.31877 2.7694 0.71474The following doesn't work, because it tries to create a new variable that's not the same height as the existing table: >> t.norm = NaN Error using table/subsasgnDot (line 136) To assign to or create a variable in a table, the number of rows must match the height of the table. It's possible that this could "scalar expand" the NaN, and create a 5x1 column of NaNs, but there are likely to be unintended side effects of that. We'd have to consider all of those. The following says, "create a new variable and assign NaN to all of its elements":

>> t.norm(:) = NaN t = x y z norm _______ ________ _________ ____________ 0.53767 -1.3077 -1.3499 [1x0 double] 1.8339 -0.43359 3.0349 [1x0 double] -2.2588 0.34262 0.7254 [1x0 double] 0.86217 3.5784 -0.063055 [1x0 double] 0.31877 2.7694 0.71474 [1x0 double]What's happened is that the new variable started out as 5x0, and NaN was assigned into all of its elements, but since it had no elements, it remained empty. The [1x0 double] you see is sort of a display artifact; there's not much other way to display 5 rows of an empty matrix. But norm is a 5x0 array. Here's what you can do to get the scalar expansion that you're looking for -- assign to the first column of the new variable:

>> t.norm(:,1) = NaN t = x y z norm _______ ________ _________ ____ 0.53767 -1.3077 -1.3499 NaN 1.8339 -0.43359 3.0349 NaN -2.2588 0.34262 0.7254 NaN 0.86217 3.5784 -0.063055 NaN 0.31877 2.7694 0.71474 NaNIn my experience, most (almost all?) of the time when you need to create a new variable like this, you're going to want NaN or zero, and so the assignment

>> t.norm = NaN(height(t),1)does the trick. I will still suggest that you're better off doing the sort of thing you mentioned in your followup, but I recognize that that may not always be possible. Hope this helps.

**27**of 30

**28**of 30

**29**of 30

**30**of 30

## Recent Comments