492 lines
11 KiB
Plaintext
492 lines
11 KiB
Plaintext
---
|
||
engine: julia
|
||
---
|
||
|
||
```{julia}
|
||
#| error: false
|
||
#| echo: false
|
||
#| output: false
|
||
using InteractiveUtils
|
||
import QuartoNotebookWorker
|
||
Base.stdout = QuartoNotebookWorker.with_context(stdout)
|
||
```
|
||
|
||
```{julia}
|
||
#| error: false
|
||
#| echo: false
|
||
#| output: false
|
||
flush(stdout)
|
||
```
|
||
|
||
# Input and Output
|
||
|
||
|
||
```{julia}
|
||
#| error: false
|
||
#| echo: false
|
||
#| output: false
|
||
|
||
# https://github.com/JuliaLang/julia/blob/master/base/show.jl#L516-L520
|
||
# https://github.com/JuliaLang/julia/blob/master/base/show.jl#L3073-L3077
|
||
|
||
|
||
using InteractiveUtils
|
||
import QuartoNotebookWorker
|
||
Base.stdout = QuartoNotebookWorker.with_context(stdout)
|
||
myactive_module() = Main.Notebook
|
||
Base.active_module() = myactive_module()
|
||
```
|
||
|
||
|
||
## Console
|
||
|
||
The operating system normally provides 3 channels (_streams_) for a program:
|
||
|
||
- Standard input channel `stdin`
|
||
- Standard output channel `stdout` and
|
||
- Standard error output channel `stderr`.
|
||
|
||
When the program is started in a terminal (or console or shell), the program can read keyboard input via `stdin` and output appears in the terminal via `stdout` and `stdout`.
|
||
|
||
|
||
- Writing to `stdout`: `print()`,`println()`,`printstyled()`
|
||
- Writing to `stderr`: `print(strerr,...)`, `println(stderr,...)`, `printstyled(stderr,...)`
|
||
- Reading from `stdin`: `readline()`
|
||
|
||
|
||
### Input
|
||
|
||
The language _Python_ provides a function `input()`:
|
||
```{.python}
|
||
ans = input("Please enter a positive number!")
|
||
```
|
||
The function prints the prompt, waits for input, and returns the
|
||
input as a `string`.
|
||
|
||
|
||
In Julia, you can implement this function as follows:
|
||
|
||
```{julia}
|
||
function input(prompt = "Input:")
|
||
println(prompt)
|
||
flush(stdout)
|
||
return chomp(readline())
|
||
end
|
||
```
|
||
|
||
**Comments**
|
||
|
||
- Write instructions are buffered by modern operating systems. With `flush(stdout)`, the buffer is emptied and the write operation is forced to complete immediately.
|
||
- `readline()` returns a string ending with a newline `\n`. The function `chomp()` removes a possible line break from the end of a string.
|
||
|
||
```{julia}
|
||
#| eval: false
|
||
a = input("Please enter 2 numbers!")
|
||
```
|
||
```{julia}
|
||
#| echo: false
|
||
a = "34 56"
|
||
```
|
||
|
||
|
||
### Processing the Input
|
||
|
||
> `split(str)` splits a string into "words" and returns an _(array of strings)_:
|
||
|
||
```{julia}
|
||
av = split(a)
|
||
```
|
||
|
||
|
||
> `parse(T, str)` tries to convert `str` to type `T`:
|
||
|
||
|
||
```{julia}
|
||
v = parse.(Int, av)
|
||
```
|
||
|
||
`parse()` generates an error if the string cannot be parsed as a value of type `T`. You can catch the error with
|
||
`try/catch` or use the function `tryparse(T, str)`, which returns `nothing` in such a case - on which you can then
|
||
e.g. test with `isnothing()`.
|
||
|
||
|
||
|
||
|
||
### Reading Individual Keystrokes
|
||
|
||
- `readline()` and similar functions wait for the input to be completed by pressing the `Enter` key.
|
||
- Techniques for reading individual _keystrokes_ can be found here:
|
||
|
||
- [https://stackoverflow.com/questions/56888266/how-to-read-keyboard-inputs-at-every-keystroke-in-julia](https://stackoverflow.com/questions/56888266/how-to-read-keyboard-inputs-at-every-keystroke-in-julia)
|
||
- [https://stackoverflow.com/questions/60954235/how-can-i-test-whether-stdin-has-input-available-in-julia](https://stackoverflow.com/questions/60954235/how-can-i-test-whether-stdin-has-input-available-in-julia)
|
||
|
||
|
||
|
||
|
||
## Formatted Output with the `Printf` Macro
|
||
|
||
Often you want to output numbers or strings with a strict format specification - total length, decimal places, right/left-aligned, etc.
|
||
|
||
To this end, the `Printf` package defines the macros `@sprintf` and `@printf`, which work very similarly to the corresponding C functions.
|
||
|
||
```{julia}
|
||
using Printf
|
||
|
||
x = 123.7876355638734
|
||
|
||
@printf("Output right-aligned with max. 10 character width and 3 decimal places: x= %10.3f", x)
|
||
```
|
||
|
||
|
||
The first argument is a string containing placeholders (here: `%10.3`) for variables to be output; followed by these variables as further arguments.
|
||
|
||
Placeholders have the form
|
||
```
|
||
%[flags][width][.precision]type
|
||
```
|
||
where the entries in square brackets are all optional.
|
||
|
||
**Type specifications in placeholders**
|
||
|
||
| | |
|
||
|:--|:------------|
|
||
|`%s`| `string`|
|
||
|`%i`| `integer`|
|
||
|`%o`| `integer octal (base=8)`|
|
||
|`%x, %X`| `integer hexadecimal (base=16) with digits 0-9abcdef or 0-9ABCDEF, resp.`|
|
||
|`%f`| `floating point number`|
|
||
|`%e`| `floating point number, scientific representation`|
|
||
|`%g`| `floating point, uses %f or %e depending on value`|
|
||
|
||
: {.striped .hover}
|
||
|
||
|
||
**Flags**
|
||
|
||
| | |
|
||
|:----|:-----|
|
||
|Plus sign| right-aligned (default)|
|
||
|Minus sign| left-aligned|
|
||
|Zero| with leading zeros|
|
||
|
||
: {.striped .hover}
|
||
|
||
|
||
**Width**
|
||
|
||
```
|
||
Number of minimum characters used (more will be taken if necessary)
|
||
```
|
||
|
||
|
||
### Examples:
|
||
|
||
|
||
```{julia}
|
||
using Printf # Don't forget to load the package!
|
||
```
|
||
|
||
|
||
```{julia}
|
||
@printf("|%s|", "Hello") # string with placeholder for string
|
||
```
|
||
The vertical bars are not part of the placeholder. They are intended to indicate the boundaries of the output field.
|
||
|
||
|
||
```{julia}
|
||
@printf("|%10s|", "Hello") # Minimum length, right-aligned
|
||
```
|
||
|
||
|
||
```{julia}
|
||
@printf("|%-10s|", "Hello") # left-aligned
|
||
```
|
||
|
||
|
||
```{julia}
|
||
@printf("|%3s|", "Hello") # Length specification can be exceeded
|
||
# Better a 'badly formatted' table than incorrect values!
|
||
```
|
||
|
||
|
||
```{julia}
|
||
j = 123
|
||
k = 90019001
|
||
l = 3342678
|
||
|
||
@printf("j= %012i, k= %-12i, l = %12i", j, k, l) # 0-flag for leading zeros
|
||
```
|
||
|
||
`@printf` and `@sprintf` can be called like functions or as macros:
|
||
|
||
```{julia}
|
||
@printf("%i %i", 22, j)
|
||
```
|
||
|
||
-- or as macros, i.e., without function parentheses and without comma:
|
||
|
||
|
||
```{julia}
|
||
@printf "%i %i" 22 j
|
||
```
|
||
|
||
`@printf` can take a stream as its first argument.
|
||
|
||
Otherwise, the argument list consists of
|
||
|
||
- format string with placeholders
|
||
- variables in the order of the placeholders, matching in number and type to the placeholders
|
||
|
||
|
||
```{julia}
|
||
@printf(stderr, "First result: %i %s\nSecond result %i",
|
||
j, "(estimated)" ,k)
|
||
```
|
||
|
||
The macro `@sprintf` does not print anything but returns the filled formatted string:
|
||
|
||
|
||
```{julia}
|
||
str = @sprintf("x = %10.6f", π );
|
||
```
|
||
|
||
|
||
```{julia}
|
||
str
|
||
```
|
||
|
||
### Formatting Floating Point Numbers:
|
||
|
||
Meaning of the _precision_ value:
|
||
|
||
- `%f` and `%e` format: maximum number of decimal places
|
||
- `%g` format: maximum number of digits output (integer + decimal places)
|
||
|
||
|
||
```{julia}
|
||
x = 123456.7890123456
|
||
|
||
@printf("%20.4f %20.4e", x, x) # 4 decimal places
|
||
```
|
||
|
||
|
||
```{julia}
|
||
@printf("%20.7f %20.7e", x, x) # 7 decimal places
|
||
```
|
||
|
||
|
||
```{julia}
|
||
@printf("%20.7g %20.4g", x, x) # total 7 and 4 digits respectively
|
||
```
|
||
|
||
## File Operations
|
||
|
||
Files are
|
||
|
||
- opened $\Longrightarrow$ a new _stream_-object is created (in addition to `stdin, stdout, stderr`)
|
||
- then this _stream_ can be read from and written to
|
||
- closed $\Longrightarrow$ _stream_-object is detached from file
|
||
|
||
```{.julia}
|
||
stream = open(path, mode)
|
||
```
|
||
|
||
- path: filename/path
|
||
- mode:
|
||
|
||
```
|
||
"r" read, opens at file beginning
|
||
"w" write, opens at file beginning (file is created or overwritten)
|
||
"a" append, opens to continue writing at file end
|
||
```
|
||
|
||
Let's write a file:
|
||
|
||
```{julia}
|
||
file = open("datei.txt", "w")
|
||
```
|
||
|
||
|
||
```{julia}
|
||
@printf(file, "%10i\n", k)
|
||
```
|
||
|
||
|
||
```{julia}
|
||
println(file, " second line")
|
||
```
|
||
|
||
|
||
```{julia}
|
||
close(file)
|
||
```
|
||
|
||
Let's look at the file:
|
||
|
||
```{julia}
|
||
;cat datei.txt
|
||
```
|
||
|
||
...and now we open it again for reading:
|
||
|
||
```{julia}
|
||
stream = open("datei.txt", "r")
|
||
```
|
||
|
||
`readlines(stream)` returns all lines of a text file as a vector of strings.
|
||
|
||
`eachline(stream)` returns an iterator over the lines of the file.
|
||
|
||
|
||
```{julia}
|
||
n = 0
|
||
for line in eachline(stream) # Read line by line
|
||
n += 1
|
||
println(n, line) # Print with line number
|
||
end
|
||
close(stream)
|
||
```
|
||
|
||
## Packages for File Formats
|
||
|
||
For input and output in various file formats, there are Julia packages, e.g.,
|
||
|
||
- [PrettyTables.jl](https://ronisbr.github.io/PrettyTables.jl/stable/) Output of formatted tables
|
||
- [DelimitedFiles.jl](https://docs.julialang.org/en/v1/stdlib/DelimitedFiles/) Input and output of matrices, etc.
|
||
- [CSV.jl](https://csv.juliadata.org/stable/) Input and output of "comma-separated values" files, etc.
|
||
- [XLSX.jl](https://felipenoris.github.io/XLSX.jl/stable/tutorial/) Input and output of Excel files
|
||
|
||
and many more...
|
||
|
||
### DelimitedFiles.jl
|
||
|
||
This package enables convenient saving/reading of matrices. It provides the functions `writedlm()` and `readdlm()`.
|
||
|
||
```{julia}
|
||
using DelimitedFiles
|
||
```
|
||
|
||
|
||
We generate a 200×3 matrix of random numbers
|
||
```{julia}
|
||
A = rand(200,3)
|
||
```
|
||
|
||
and save it
|
||
```{julia}
|
||
f = open("data2.txt", "w")
|
||
writedlm(f, A)
|
||
close(f)
|
||
```
|
||
|
||
|
||
The written file starts like this:
|
||
|
||
```{julia}
|
||
;head data2.txt
|
||
```
|
||
|
||
|
||
Reading it back is even simpler:
|
||
```{julia}
|
||
B = readdlm("data2.txt")
|
||
```
|
||
|
||
|
||
One more point: In Julia, the `do` notation is often used for file handling, see @sec-do.
|
||
This uses the fact that `open()` also has methods where the 1st argument is a `function(iostream)`.
|
||
This function is then applied to the _stream_ and the stream is automatically closed at the end. The `do` notation allows you to
|
||
define this function anonymously after the `do`:
|
||
|
||
```{julia}
|
||
open("data2.txt", "w") do io
|
||
writedlm(io, A)
|
||
end
|
||
```
|
||
|
||
### CSV and DataFrames
|
||
|
||
- The CSV format is often used to provide tables in a form that can be read not only by MS Excel.
|
||
- An example is the weather and climate database _Meteostat_.
|
||
- The [DataFrames.jl](https://dataframes.juliadata.org/stable/) package provides functions for convenient handling of tabular data.
|
||
|
||
|
||
```{julia}
|
||
using CSV, DataFrames, Downloads
|
||
# Weather data from Westerland, see https://dev.meteostat.net/bulk/hourly.html
|
||
|
||
url = "https://bulk.meteostat.net/v2/hourly/10018.csv.gz"
|
||
http_response = Downloads.download(url)
|
||
file = CSV.File(http_response, header=false);
|
||
```
|
||
|
||
|
||
The data looks like this:
|
||
|
||
|
||
```{julia}
|
||
# https://dev.meteostat.net/bulk/hourly.html#endpoints
|
||
#
|
||
# Column 1 Date
|
||
# 2 Time (hour)
|
||
# 3 Temperature
|
||
# 5 Humidity
|
||
# 6 Precipitation
|
||
# 8 Wind direction
|
||
# 9 Wind speed
|
||
|
||
df = DataFrame(file)
|
||
```
|
||
|
||
|
||
|
||
|
||
```{julia}
|
||
#| error: false
|
||
#| echo: false
|
||
#| output: false
|
||
#| eval: false
|
||
describe(df)
|
||
```
|
||
|
||
|
||
|
||
|
||
For convenient plotting and handling of date and time formats in the weather table,
|
||
we load two helper packages:
|
||
|
||
```{julia}
|
||
using StatsPlots, Dates
|
||
```
|
||
|
||
|
||
We create a new column that combines date (from column 1) and time (from column 2):
|
||
|
||
```{julia}
|
||
# new column combining col. 1 and 2 (date & time)
|
||
|
||
df[!, :datetime] = DateTime.(df.Column1) .+ Hour.(df.Column2);
|
||
```
|
||
|
||
|
||
|
||
|
||
```{julia}
|
||
#| error: false
|
||
#| echo: false
|
||
#| output: false
|
||
#| eval: false
|
||
|
||
@df df plot(:datetime, :Column3)
|
||
```
|
||
|
||
And now to the plot:
|
||
|
||
```{julia}
|
||
@df df plot(:datetime, [:Column9, :Column6, :Column3],
|
||
xlims = (DateTime(2023,9,1), DateTime(2024,5,30)),
|
||
layout=(3,1), title=["Wind" "Rain" "Temp"],
|
||
legend=:none, size=(800,800))
|
||
```
|
||
|