In-Depth Look At Data Structures In Julia | by Emmett Boudreau | Towards Data Science

Video For This article:
Github repository
Notebook
In the previous iteration of the comprehensive Julia tutorials, we went over how to use Julia’s multiple dispatch with types and functions. Multiple dispatch is a simple system used to apply different types to different function calls under the same method argument. The key component in that structure is types, and more specifically, data types.
Datatypes are the bread and butter of computer programming. Any programming work is going to involve manipulating, moving, and working with basic datatypes. Types themselves that are created in the language are simply containers for other types that are usually data types.
The first kind of data we can store inside of the Julia programming language is a basic datatype. Basic datatypes include data such as numbers, text, characters, and boolean.
A boolean, or bool type is a type that indicates whether or not a condition is true. Booleans can be represented with both a true/false value that ultimately condenses into a 1 or a 0, or just an integer (1 or 0) that represents the condition of the type.
typeof(true)Bool
Likewise, we can also assert booleans to integers representing true or false in the Julia language:
Bool(1)true
The integer datatype in programming parallels the integer datatype in mathematics. An integer is a whole number with no fractional value. Typically, whenever we are working with integers in Julia, we will be working with the Int64 datatype. This means that the integer has 64-bits. Alternatively, there are also Int32s and BigInts.
typeof(5)Int64
Floats are integers with a second number beyond the decimal point, or a fractional value. Floats can be thought of as two separate pieces of data, the integer before the fractional value, and the fractional value coming in after the integer.
typeof(5.5)Float64
Complex floats come into play quite frequently inside of the Julia language. A complex float is simply a float that has values that are both imaginary as well as real. It is important to note that this means that these types are not real. Often scientific computing is going to require a real datatype, as a result you might need to assert some complexes to floats.
typeof(5.5 + 5.5im)Complex(Float64)
The “ im” in the second part of this datatype denotes that the numbers prior are imaginary.
Big datatypes on the other hand, are real numbers but go beyond the capabilities of most 64-bit applications with integers and floats and are accurate. This is a significant exclusivity to the Julia language, as it allows for enormous integer and float datatypes to be passed through multiple functions without a problem — something that few languages are capable of.
# Big Intbig(51515235151351335)# Big Floatbig(5.4172473471347374147)
The symbol datatype is another great thing that the Julia language has pulled from the functional paradigm. Symbols can be used to represent everything from parameters to dictionary keys — and they are reasonably good at their job. In general, the symbol datatype is a great thing to have in a programming language because it can be used as a representation for essentially anything, hence the name: Symbol. Symbols can be written in Julia by simply placing a colon in front of a key-word, for example:
typeof(:Symbol)Symbol
Strings are a fairly simple concept to wrap ones head around — a string is a set of consecutive unicode/ascii characters that make up a composition of chars. As demonstrated in part 3 — Loops, strings can be looped through to reveal the next datatype, chars. If you have yet to read part 3, you can check it out here:
towardsdatascience.com
Strings are delimited with quotation marks. Chars, on the other hand are delimited with apostrophes.
typeof("Hello")Stringtypeof('h')char
Floats are used to represent characters in ASCII. As a result, we can always asset chars into floats:
float('5')
Fun fact: This is how LabelEncoding works
All categorical, vector and matrix data is simply going to act as a container for other types. Consider the example of an array which is comprised of multiple datatypes, such as integers, floats, booleans, or strings.
element_wise = [5, 10, 15, 15]
This new array is an array containing 4 Int64s. We can index them with numbers based on their position in the array:
element_wise[1] == 5trueelement_wise[2] == 10true
We can also see this demonstrated in the type:
typeof(element_wise)Array{Int64, 1}
Why brackets are important:
You might have noticed that whenever an array is created, it is typically within the bounds of square brackets. Without these brackets, the type of this collection of data would be a Tuple. Although I didn’t go into depth on tuples, they are a datatype that is quite valid — but certainly not what you want to have if you need an array.
h = 5,10,15,20typeof(h)NTuple{4, Int64}
Furthermore, a lack of using brackets can make it impossible for a function to understand which parameters are corresponding to which variable. Consider the append!() method. The append! method takes two parameters, firstly whatever data you would like to append to, and secondly the data you would like to append to it. This works if we were to use brackets, as the array is seen as one argument. However, using comma will present an ArgumentError.
         parameters           v    vappend!([5,10], 15)      parameters       v  v  vappend!(5,10,15)
In Julia, dictionaries need to be explicitly defined as such — otherwise it will return a tuple. This is a very valuable and dynamic type, and that is further exemplified when working with data-sets or JSON data.
data = Dict(:A => [5,10,15], :B => [11, 12, 13)
Using a dictionary, we can call data by calling corresponding symbol keys, in this example:
data[:A][5, 10, 15]
Pairs are actually tuple types, but it is important to realize that a pair is a tuple, but a tuple is not a pair. A pair is the same exact thing that we used to access parts of our dictionary a second ago, and can be created in the same way:
key = :A => [5,10,15]
Sets are simply unique values extracted from a type. For example, if we were to assert the set type to an array, we would get each of the unique values in that array:
arr = [5, 5, 7, 7, 6, 4, 5]set = Set(arr)println(set)[5, 7, 6, 4]
Tuples are generalized structures for datatypes that don’t necessarily have a defined structure. Tuples can be thought of as a less-organized array.
h = 5, 10, 15
Sometimes working with raw datatypes can be tedious and exhausting, especially whenever a function needs to work with many parameters. This is where structs come in. Creating a struct will make a new type that can hold arbitrary and predefined data structures. We can create a struct by using the struct keyword, followed by a definition and data.
struct typer    h    vend
With this type, we can now assign a new variable to it, providing the necessary data as parameters for the constructor:
w = typer(5, 10)
In this instance, the struct typer is our new type that holds the data h and v. We can access this data by calling struct.data:
typer.h5typer.v10
We can also pass this new type through a function, for example adding our h and v data from our typer struct:
function addtyper(typer)    return(typer.h + typer.v)end
It is important to note that the data in constructed types in the Julia language will be immutable. This can be changed by placing the keyword mutable before the type:
mutable struct typer    h    vend
Datatypes are consistently a very important thing in programming, and this issue is felt further in the world of data science. Most of programming is simply using data structures and constructed types to work with datatypes. That being said, understanding datatypes is very important to a programmer. There are lots of types in Julia, and some that you can construct yourself, but learning how to utilize and manipulate these types is going to be the key to being a great programmer.