Macros in Julia are super useful for defining domain specific languages
and this is taken advantage of by many packages like JuMP.jl, StatsModels.jl,
DataFramesMeta.jl, DataFrameMacros.jl, ….
This post was prompted by the discussion in this issue and is aimed to
highlight how macros should be properly invoked.
The examples were tested under Julia 1.7.0.
Preliminaries
A big advantage of macros is that they do not require parentheses when they are
called, e.g.:
The rules of both types of invocation are explained in the Julia Manual:
Macros are invoked with the following general syntax:
@name expr1 expr2 ...
@name(expr1, expr2, ...)
Note the distinguishing @ before the macro name and the lack of commas between
the argument expressions in the first form, and the lack of whitespace after
@name in the second form.
The explanation seems clear. However, sometimes it is tricky to tell what Julia
considers to be an expression. Let me give some examples.
Examples of non-obvious expression handling
I think the issue is best explained with this basic macro:
As you can see above when you write 1 + 1 and 1+1 then Julia treats it
as a single expression. However if you write 1 +1 then Julia considers it
to be two expressions.
In the first case a parenthesized style of macro call was used and we see that
the @m macro received two arguments. In @m (1, 1) since we put a space
after @m the (1, 1) is considered to be a tuple that was passed to it as a
single argument. Writing @m 1, 1 is interpreted in the same way, as when
defining a tuple you can omit passing parenthesis. Finally @m 1 1 is again
interpreted as passing two arguments to @m because the first and the second 1 are separate expressions.
Conclusions
When writing macros always make sure to take care of understanding where the
boundaries of the expressions passed to it are or use the macro invocation style
that uses parentheses.
Let me give one final example. If you want to get the time in minutes that some
operation took do not write:
Julia includes many packages that could be used in operation research and optimization in general. This article serves as a brief introduction to different packages available in Julia’s ecosystem. I will focus on two packages that are stepping-stones in future work: DataFrames.jl and PyPlots. DataFrames.jl provides a set of tools for working with tabular data similar to Pandas in Python. The PyPlots module provides a Julia interface to the Matplotlib plotting library from Python. Although PyPlots is a distribution of Python and Matplotlib, Julia can install a private distribution that can’t be accessed outside Julia’s environment.
Adding the necessary packages
Open a new terminal window and run Julia. Use the code below to add the packages required for this article.
julia> using Pkg
julia> Pkg.add("DataFrames")
julia> Pkg.add("CSV")
julia> Pkg.add("Arrow")
Open a new terminal window and run Julia. Initialize the PYTHON environment variable:
julia> ENV["PYTHON"] = ""
""
Install PyPlot:
julia> using Pkg
julia> Pkg.add("PyPlot")
After adding all the required packages using Julia REPL, the following code is used to import the packages in the Jupyter editor (i.e. Jupyter Notebook).
using DataFrames
using CSV
using Arrow
using PyPlot
DataFrames requires some packages in the backend like CSV and Arrow to complete its operations properly. Thus, these packages were added initially in the section before.
Because PyPlots is an interface for Matplotlib in Julia, all the documentation is available on Matplotlib’s main page.
Covid-19 showcase using Julia
The first covid-19 case was detected on the 17th of November, 2019. Although two years passed since the pandemic started, the virus is still persisting and cases are increasing exponentially. Thus, data analysis is important to understand the growth of cases around the world. I will be using a .csv file containing data about the cases in country X. The file is available in a public repository on my GitHub page, so I can copy it to an excel sheet and move it to the directory file where the Jupyter notebook is located. Also, I can import the data from the web using excel and attach the link of the raw format of the database on GitHub. In both cases, saving the file as time.csv is necessary to fit with the code in later stages.
Here, I am showing the database, in csv format, that I opened via excel on my desktop.
I will read the csv file, in the Jupyter notebook, using the code block below:
df = CSV.File("time.csv") |> DataFrame; # reading the csv file using CSV package and changing it to a DataFrame using the arrow operation |>
df[1:5,:] # output the first five rows
Importing data files is smoother using DataFrames. Some of the operations present in the code blocks below are explained in a previous post introducing Julia. Plotting is another important tool to understand data and visualize it better. Matplotlib is introduced before to the blog but in Python.
The code block below creates a bar chart showing the number of cumulative tests and cumulative negative cases over a period of six days from the DataFrame, df, imported above.
y1 = df[20:25,2];
y2 = df[20:25,3];
x = df[20:25,1];
fig = plt.figure()
ax = plt.subplot()
ax.bar(x, y1, label="cumulative tests",color="black")
ax.bar(x, y2, label="cumulative negative cases",color="grey")
ax.set_title("Covid-19 data in Country X",fontsize=18,color="green")
ax.set_xlabel("date",fontsize=14,color="red")
ax.set_ylabel("number of cases",fontsize=14,color="red")
ax.legend(fontsize=10)
ax.grid(b=1,color="blue",alpha=0.1)
plt.show()
The bar chart above could be improved by allocating a bar for each category. This is done using the code block below.
barWidth = 0.25
br1 = 1:1:length(x)
br2 = [x + barWidth for x in br1]
fig = plt.figure()
ax = plt.subplot()
ax.bar(br1, y1,color="r", width=barWidth, edgecolor ="grey",label="cumulative tests")
ax.bar(br2, y2,color ="g",width=barWidth, edgecolor ="grey", label="cumulative negative cases")
ax.set_title("Covid-19 data in Country X",fontsize=18,color="green")
ax.set_xlabel("date", fontweight ="bold",fontsize=14)
ax.set_ylabel("number of cases", fontweight ="bold",fontsize=14)
ax.legend(fontsize=10)
ax.grid(b=1,color="blue",alpha=0.1)
plt.xticks([r + barWidth for r in br1], ["2/8/2020", "2/9/2020", "2/10/2020", "2/11/2020", "2/12/2020","2/13/2020"])
plt.legend()
plt.show()
Understanding the exponential growth associated with covid19 requires plotting the whole dataset which is over the span of 44 days. Therefore, the next code block aims to show the covid cases of the complete dataset.
days = 1:1:44
days_array = collect(days)
confirmed_cases = df[:,4]
fig = plt.figure(figsize=(10,10))
ax = plt.subplot()
ax.bar(days_array,confirmed_cases , color ="blue",width = 0.4)
ax.set_title("A bar chart showing cumulative confirmed covid-19 cases in country X",fontsize=22,color="darkgreen")
ax.set_xlabel("day number",fontsize=16,color="darkgreen")
ax.set_ylabel("number of cases",fontsize=16,color="darkgreen")
ax.xaxis.set_ticks_position("none")
ax.yaxis.set_ticks_position("none")
ax.xaxis.set_tick_params(pad = 5)
ax.yaxis.set_tick_params(pad = 10)
ax.grid(b = 1, color ="grey",linestyle ="-.", linewidth = 0.5,alpha = 0.2)
fig.text(0.3, 0.8, "SCDA-JaafarBallout", fontsize = 10,color ="grey", ha ="right", va ="bottom",alpha = 0.6)
plt.show()
Even though bar charts are powerful in our case, it is still nice to observe the exponential curve. For that, a code block is presented below to plot the growth curve of confirmed cases.
fig = plt.figure(figsize=(10,10))
ax = plt.subplot()
ax.plot(days_array,confirmed_cases , color ="blue",marker="o",markersize=6, linewidth=2, linestyle ="--")
ax.set_title("A bar chart showing cumulative confirmed covid-19 cases in country X",fontsize=22,color="darkgreen")
ax.set_xlabel("day number",fontsize=16,color="darkgreen")
ax.set_ylabel("number of cases",fontsize=16,color="darkgreen")
ax.grid(b = 1, color ="grey",linestyle ="-.", linewidth = 0.5,alpha = 0.2)
fig.text(0.3, 0.8, "SCDA-JaafarBallout", fontsize = 10,color ="grey", ha ="right", va ="bottom",alpha = 0.6)
plt.xticks(size=16, color ="black")
plt.yticks(size=16, color ="black")
plt.show()
Realizing a meme! (with Julia)
Recently, this meme got viral. So, it is nice to figure the missing functions by plotting.
Graphing networks by hand or traditional software seems exhausting. In future posts, I will demonstrate how to draw complicated networks using Julia and its dependencies. Then, I will solve the network problem using the JuMP package.