Commit e038f7c9 authored by Trevor Bekolay's avatar Trevor Bekolay
Browse files

Prefer functions over methods.

Most methods on NumPy arrays also have an equivalent NumPy functions
that takes the array as input. I have noticed that many novice
programmers prefer function (which have a pretty clear input-output
relationship) to methods (which have an implicit input).
This commit changes all instances of calling methods on arrays to
calling the equivalent NumPy function instead. I tried to simplify
the explanation in `01-numpy.md` as well, which hopefully does not
significantly affect the flow of the prose.

I also added a "function" entry to the glossary and linked to it in
`01-numpy.md`.
parent 52780974
......@@ -50,7 +50,7 @@ array([[ 0., 0., 1., ..., 3., 0., 0.],
~~~
The expression `numpy.loadtxt(...)` is a [function call](reference.html#function-call)
that asks Python to run the function `loadtxt` which belongs to the `numpy` library.
that asks Python to run the [function](reference.html#function) `loadtxt` which belongs to the `numpy` library.
This [dotted notation](reference.html#dotted-notation) is used everywhere in Python
to refer to the parts of things as `thing.component`.
......@@ -159,18 +159,20 @@ This is different from the way spreadsheets work.
> ## Who's who in the memory {.callout}
>
>You can use the `%whos` command at any time to see what variables you have created and what modules you have loaded into the computers memory. As this is an IPython command, it will only work if you are in an IPython terminal or the Jupyter Notebook.
> You can use the `%whos` command at any time to see what
> variables you have created and what modules you have loaded into the computer's memory.
> As this is an IPython command, it will only work if you are in an IPython terminal or the Jupyter Notebook.
>
>~~~ {.python}
>%whos
>~~~
>~~~ {.output}
>Variable Type Data/Info
>--------------------------------
>numpy module <module 'numpy' from '/Us<...>kages/numpy/__init__.py'>
>weight_kg float 100.0
>weight_lb float 126.5
>~~~
> ~~~ {.python}
> %whos
> ~~~
> ~~~ {.output}
> Variable Type Data/Info
> --------------------------------
> numpy module <module 'numpy' from '/Us<...>kages/numpy/__init__.py'>
> weight_kg float 100.0
> weight_lb float 126.5
> ~~~
Just as we can assign a single value to a variable, we can also assign an array of values
to a variable using the same syntax. Let's re-run `numpy.loadtxt` and save its result:
......@@ -384,37 +386,48 @@ tripledata:
~~~
Often, we want to do more than add, subtract, multiply, and divide values of data.
Arrays also know how to do more complex operations on their values.
NumPy knows how to do more complex operations on arrays.
If we want to find the average inflammation for all patients on all days,
for example,
we can just ask the array for its mean value:
we can ask NumPy to compute `data`'s mean value:
~~~ {.python}
print(data.mean())
print(numpy.mean(data))
~~~
~~~ {.output}
6.14875
~~~
`mean` is a [method](reference.html#method) of the array,
i.e.,
a function that belongs to it
in the same way that the member `shape` does.
If variables are nouns, methods are verbs:
they are what the thing in question knows how to do.
We need empty parentheses for `data.mean()`,
even when we're not passing in any parameters,
to tell Python to go and do something for us. `data.shape` doesn't
need `()` because it is just a description but `data.mean()` requires the `()`
because it is an action.
NumPy arrays have lots of useful methods.
Let's use three of those methods to get some descriptive values about the dataset.
`mean` is a [function](reference.html#function) that takes
an array as an [argument](reference.html#argument).
If variables are nouns, functions are verbs:
they do things with variables.
> ## Not all functions have input {.callout}
>
> Generally, a function uses inputs to produce outputs.
> However, some functions produce outputs without
> needing any input. For example, generating a random number
> between 0 and 1 doesn't require any input.
>
> ~~~ {.python}
> print(numpy.random.random())
> ~~~
> ~~~ {.output}
> 0.1307966624414867
> ~~~
>
> For functions that don't take in any arguments,
> we still need parentheses (`()`)
> to tell Python to go and do something for us.
NumPy has lots of useful functions that take an array as input.
Let's use three of those functions to get some descriptive values about the dataset.
We'll also use multiple assignment,
a convenient Python feature that will enable us to do this all in one line.
~~~ {.python}
maxval, minval, stdval = data.max(), data.min(), data.std()
maxval, minval, stdval = numpy.max(data), numpy.min(data), numpy.std(data)
print('maximum inflammation:', maxval)
print('minimum inflammation:', minval)
......@@ -426,20 +439,17 @@ minimum inflammation: 0.0
standard deviation: 4.61383319712
~~~
> ## Mystery methods in IPython {.callout}
> ## Mystery functions in IPython {.callout}
>
> How did we know what methods data has and how to use them? When you are working
> on your own data it might be something different to a numpy object: how will
> you know what methods you can use then? If you are working in the IPython/Jupyter
> Notebook there is an easy way to find out. If you type the name of your object
> with a full-stop then you can use tab completion (e.g. type `data.` and then press tab)
> to see a list of all methods that you can use on that object. After selecting one you
> can also add a question mark (e.g. `data.cumprod?`) and IPython will return an
> explanation of the method! This is the same as doing `help(data.cumprod)`..
When analyzing data,
though,
> How did we know what functions NumPy has and how to use them?
> If you are working in the IPython/Jupyter Notebook there is an easy way to find out.
> If you type the name of something with a full-stop then you can use tab completion
> (e.g. type `numpy.` and then press tab)
> to see a list of all functions and attributes that you can use. After selecting one you
> can also add a question mark (e.g. `numpy.cumprod?`) and IPython will return an
> explanation of the method! This is the same as doing `help(numpy.cumprod)`.
When analyzing data, though,
we often want to look at partial statistics,
such as the maximum value per patient
or the average value per day.
......@@ -460,10 +470,10 @@ Comments allow programmers to leave explanatory notes for other
programmers or their future selves.
We don't actually need to store the row in a variable of its own.
Instead, we can combine the selection and the method call:
Instead, we can combine the selection and the function call:
~~~ {.python}
print('maximum inflammation for patient 2:', data[2, :].max())
print('maximum inflammation for patient 2:', numpy.max(data[2, :]))
~~~
~~~ {.output}
maximum inflammation for patient 2: 19.0
......@@ -477,12 +487,12 @@ operation across an axis:
![Operations Across Axes](fig/python-operations-across-axes.png)
To support this,
most array methods allow us to specify the axis we want to work on.
most array functions allow us to specify the axis we want to work on.
If we ask for the average across axis 0 (rows in our 2D example),
we get:
~~~ {.python}
print(data.mean(axis=0))
print(numpy.mean(data, axis=0))
~~~
~~~ {.output}
[ 0. 0.45 1.11666667 1.75 2.43333333 3.15
......@@ -499,7 +509,7 @@ As a quick check,
we can ask this array what its shape is:
~~~ {.python}
print(data.mean(axis=0).shape)
print(numpy.mean(data, axis=0).shape)
~~~
~~~ {.output}
(40,)
......@@ -510,7 +520,7 @@ so this is the average inflammation per day for all patients.
If we average across axis 1 (columns in our 2D example), we get:
~~~ {.python}
print(data.mean(axis=1))
print(numpy.mean(data, axis=1))
~~~
~~~ {.output}
[ 5.45 5.425 6.1 5.9 5.55 6.225 5.975 6.65 6.625 6.525
......@@ -564,7 +574,7 @@ inflammation rises and falls over a 40-day period.
Let's take a look at the average inflammation over time:
~~~ {.python}
ave_inflammation = data.mean(axis=0)
ave_inflammation = numpy.mean(data, axis=0)
ave_plot = matplotlib.pyplot.plot(ave_inflammation)
matplotlib.pyplot.show()
~~~
......@@ -581,14 +591,14 @@ we expect a sharper rise and slower fall.
Let's have a look at two other statistics:
~~~ {.python}
max_plot = matplotlib.pyplot.plot(data.max(axis=0))
max_plot = matplotlib.pyplot.plot(numpy.max(data, axis=0))
matplotlib.pyplot.show()
~~~
![Maximum Value Along The First Axis](fig/01-numpy_75_1.png)
~~~ {.python}
min_plot = matplotlib.pyplot.plot(data.min(axis=0))
min_plot = matplotlib.pyplot.plot(numpy.min(data, axis=0))
matplotlib.pyplot.show()
~~~
......@@ -606,7 +616,7 @@ You can group similar plots in a single figure using subplots.
This script below uses a number of new commands. The function `matplotlib.pyplot.figure()`
creates a space into which we will place all of our plots. The parameter `figsize`
tells Python how big to make this space. Each subplot is placed into the figure using
its `add_subplot` method. The `add_subplot` method takes 3 parameters. The first denotes
its `add_subplot` [method](reference.html#method). The `add_subplot` method takes 3 parameters. The first denotes
how many total rows of subplots there are, the second parameter refers to the
total number of subplot columns, and the final parameter denotes which subplot
your variable is referencing (left-to-right, top-to-bottom). Each subplot is stored in a
......@@ -627,13 +637,13 @@ axes2 = fig.add_subplot(1, 3, 2)
axes3 = fig.add_subplot(1, 3, 3)
axes1.set_ylabel('average')
axes1.plot(data.mean(axis=0))
axes1.plot(numpy.mean(data, axis=0))
axes2.set_ylabel('max')
axes2.plot(data.max(axis=0))
axes2.plot(numpy.max(data, axis=0))
axes3.set_ylabel('min')
axes3.plot(data.min(axis=0))
axes3.plot(numpy.min(data, axis=0))
fig.tight_layout()
......@@ -742,33 +752,33 @@ the graphs will actually be squeezed together more closely.)
>
> Arrays can be concatenated and stacked on top of one another,
> using NumPy's `vstack` and `hstack` functions for vertical and horizontal stacking, respectively.
>
>
> ~~~ {.python}
> import numpy
>
>
> A = numpy.array([[1,2,3], [4,5,6], [7, 8, 9]])
> print('A = ')
> print(A)
>
>
> B = numpy.hstack([A, A])
> print('B = ')
> print(B)
>
>
> C = numpy.vstack([A, A])
> print('C = ')
> print(C)
> ~~~
>
>
> ~~~ {.output}
> A =
> A =
> [[1 2 3]
> [4 5 6]
> [7 8 9]]
> B =
> B =
> [[1 2 3 1 2 3]
> [4 5 6 4 5 6]
> [7 8 9 7 8 9]]
> C =
> C =
> [[1 2 3]
> [4 5 6]
> [7 8 9]
......@@ -776,5 +786,7 @@ the graphs will actually be squeezed together more closely.)
> [4 5 6]
> [7 8 9]]
> ~~~
>
> Write some additional code that slices the first and last columns of `A`, and stacks them into a 3x2 array. Make sure to `print` the results to verify your solution.
\ No newline at end of file
>
> Write some additional code that slices the first and last columns of `A`,
> and stacks them into a 3x2 array.
> Make sure to `print` the results to verify your solution.
......@@ -57,13 +57,13 @@ for f in filenames:
axes3 = fig.add_subplot(1, 3, 3)
axes1.set_ylabel('average')
axes1.plot(data.mean(axis=0))
axes1.plot(numpy.mean(data, axis=0))
axes2.set_ylabel('max')
axes2.plot(data.max(axis=0))
axes2.plot(numpy.max(data, axis=0))
axes3.set_ylabel('min')
axes3.plot(data.min(axis=0))
axes3.plot(numpy.min(data, axis=0))
fig.tight_layout()
matplotlib.pyplot.show()
......
......@@ -113,7 +113,7 @@ seemed to rise like a straight line, one unit per day.
We can check for this inside the `for` loop we wrote with the following conditional:
~~~ {.python}
if data.max(axis=0)[0] == 0 and data.max(axis=0)[20] == 20:
if numpy.max(data, axis=0)[0] == 0 and numpy.max(data, axis=0)[20] == 20:
print('Suspicious looking maxima!')
~~~
......@@ -122,7 +122,7 @@ the minima per day were all zero (looks like a healthy person snuck into our stu
We can also check for this with an `elif` condition:
~~~{.python}
elif data.min(axis=0).sum() == 0:
elif numpy.sum(numpy.min(data, axis=0)) == 0:
print('Minima add up to zero!')
~~~
......@@ -137,9 +137,9 @@ Let's test that out:
~~~ {.python}
data = numpy.loadtxt(fname='inflammation-01.csv', delimiter=',')
if data.max(axis=0)[0] == 0 and data.max(axis=0)[20] == 20:
if numpy.max(data, axis=0)[0] == 0 and numpy.max(data, axis=0)[20] == 20:
print('Suspicious looking maxima!')
elif data.min(axis=0).sum() == 0:
elif numpy.sum(numpy.min(data, axis=0)) == 0:
print('Minima add up to zero!')
else:
print('Seems OK!')
......@@ -151,9 +151,9 @@ Suspicious looking maxima!
~~~ {.python}
data = numpy.loadtxt(fname='inflammation-03.csv', delimiter=',')
if data.max(axis=0)[0] == 0 and data.max(axis=0)[20] == 20:
if numpy.max(data, axis=0)[0] == 0 and numpy.max(data, axis=0)[20] == 20:
print('Suspicious looking maxima!')
elif data.min(axis=0).sum() == 0:
elif numpy.sum(numpy.min(data, axis=0)) == 0:
print('Minima add up to zero!')
else:
print('Seems OK!')
......
......@@ -189,13 +189,13 @@ def analyze(filename):
axes3 = fig.add_subplot(1, 3, 3)
axes1.set_ylabel('average')
axes1.plot(data.mean(axis=0))
axes1.plot(numpy.mean(data, axis=0))
axes2.set_ylabel('max')
axes2.plot(data.max(axis=0))
axes2.plot(numpy.max(data, axis=0))
axes3.set_ylabel('min')
axes3.plot(data.min(axis=0))
axes3.plot(numpy.min(data, axis=0))
fig.tight_layout()
matplotlib.pyplot.show()
......@@ -209,9 +209,9 @@ def detect_problems(filename):
data = numpy.loadtxt(fname=filename, delimiter=',')
if data.max(axis=0)[0] == 0 and data.max(axis=0)[20] == 20:
if numpy.max(data, axis=0)[0] == 0 and numpy.max(data, axis=0)[20] == 20:
print('Suspicious looking maxima!')
elif data.min(axis=0).sum() == 0:
elif numpy.sum(numpy.min(data, axis=0)) == 0:
print('Minima add up to zero!')
else:
print('Seems OK!')
......@@ -242,7 +242,7 @@ let's write a function to center a dataset around a particular value:
~~~ {.python}
def center(data, desired):
return (data - data.mean()) + desired
return (data - numpy.mean(data)) + desired
~~~
We could test this on our actual data,
......@@ -282,9 +282,9 @@ It's hard to tell from the default output whether the result is correct,
but there are a few simple tests that will reassure us:
~~~ {.python}
print('original min, mean, and max are:', data.min(), data.mean(), data.max())
print('original min, mean, and max are:', numpy.min(data), numpy.mean(data), numpy.max(data))
centered = center(data, 0)
print('min, mean, and and max of centered data are:', centered.min(), centered.mean(), centered.max())
print('min, mean, and and max of centered data are:', numpy.min(centered), numpy.mean(centered), numpy.max(centered))
~~~
~~~ {.output}
original min, mean, and max are: 0.0 6.14875 20.0
......@@ -298,7 +298,7 @@ The mean of the centered data isn't quite zero --- we'll explore why not in the
We can even go further and check that the standard deviation hasn't changed:
~~~ {.python}
print('std dev before and after:', data.std(), centered.std())
print('std dev before and after:', numpy.std(data), numpy.std(centered))
~~~
~~~ {.output}
std dev before and after: 4.61383319712 4.61383319712
......@@ -309,7 +309,7 @@ but we probably wouldn't notice if they were different in the sixth decimal plac
Let's do this instead:
~~~ {.python}
print('difference in standard deviations before and after:', data.std() - centered.std())
print('difference in standard deviations before and after:', numpy.std(data) - numpy.std(centered))
~~~
~~~ {.output}
difference in standard deviations before and after: -3.5527136788e-15
......@@ -328,7 +328,7 @@ The usual way to put documentation in software is to add [comments](reference.ht
~~~ {.python}
# center(data, desired): return a new array containing the original data centered around the desired value.
def center(data, desired):
return (data - data.mean()) + desired
return (data - numpy.mean(data)) + desired
~~~
There's a better way, though.
......@@ -338,7 +338,7 @@ that string is attached to the function as its documentation:
~~~ {.python}
def center(data, desired):
'''Return a new array containing the original data centered around the desired value.'''
return (data - data.mean()) + desired
return (data - numpy.mean(data)) + desired
~~~
This is better because we can now ask Python's built-in help system to show us the documentation for the function:
......@@ -362,7 +362,7 @@ we can break the string across multiple lines:
def center(data, desired):
'''Return a new array containing the original data centered around the desired value.
Example: center([1, 2, 3], 0) => [-1, 0, 1]'''
return (data - data.mean()) + desired
return (data - numpy.mean(data)) + desired
help(center)
~~~
......@@ -424,7 +424,7 @@ let's re-define our `center` function like this:
def center(data, desired=0.0):
'''Return a new array containing the original data centered around the desired value (0 by default).
Example: center([1, 2, 3], 0) => [-1, 0, 1]'''
return (data - data.mean()) + desired
return (data - numpy.mean(data)) + desired
~~~
The key change is that the second parameter is now written `desired=0.0` instead of just `desired`.
......
......@@ -140,7 +140,7 @@ def main():
script = sys.argv[0]
filename = sys.argv[1]
data = numpy.loadtxt(filename, delimiter=',')
for m in data.mean(axis=1):
for m in numpy.mean(data, axis=1):
print(m)
~~~
......@@ -169,7 +169,7 @@ def main():
script = sys.argv[0]
filename = sys.argv[1]
data = numpy.loadtxt(filename, delimiter=',')
for m in data.mean(axis=1):
for m in numpy.mean(data, axis=1):
print(m)
main()
......@@ -325,7 +325,7 @@ def main():
script = sys.argv[0]
for filename in sys.argv[1:]:
data = numpy.loadtxt(filename, delimiter=',')
for m in data.mean(axis=1):
for m in numpy.mean(data, axis=1):
print(m)
main()
......@@ -380,11 +380,11 @@ def main():
data = numpy.loadtxt(f, delimiter=',')
if action == '--min':
values = data.min(axis=1)
values = numpy.min(data, axis=1)
elif action == '--mean':
values = data.mean(axis=1)
values = numpy.mean(data, axis=1)
elif action == '--max':
values = data.max(axis=1)
values = numpy.max(data, axis=1)
for m in values:
print(m)
......@@ -441,11 +441,11 @@ def process(filename, action):
data = numpy.loadtxt(filename, delimiter=',')
if action == '--min':
values = data.min(axis=1)
values = numpy.min(data, axis=1)
elif action == '--mean':
values = data.mean(axis=1)
values = numpy.mean(data, axis=1)
elif action == '--max':
values = data.max(axis=1)
values = numpy.max(data, axis=1)
for m in values:
print(m)
......@@ -537,11 +537,11 @@ def process(filename, action):
data = numpy.loadtxt(filename, delimiter=',')
if action == '--min':
values = data.min(axis=1)
values = numpy.min(data, axis=1)
elif action == '--mean':
values = data.mean(axis=1)
values = numpy.mean(data, axis=1)
elif action == '--max':
values = data.max(axis=1)
values = numpy.max(data, axis=1)
for m in values:
print(m)
......
......@@ -111,7 +111,7 @@ the mininum and maximum values in an array:
import numpy
def span(a):
diff = a.max() - a.min()
diff = numpy.max(a) - numpy.min(a)
return diff
data = numpy.loadtxt(fname='inflammation-01.csv', delimiter=',')
......
......@@ -171,14 +171,14 @@ Solutions to exercises:
> ~~~ {.python}
> # for example:
> axes3.set_ylabel('min')
> axes3.plot(data.min(axis=0))
> axes3.plot(numpy.min(data, axis=0))
> axes3.set_ylim(0,6)
>
> # or a more automated approach:
> min_data = data.min(axis=0)
> min_data = numpy.min(data, axis=0)
> axes3.set_ylabel('min')
> axes3.plot(min_data)
> axes3.set_ylim(min_data.min(),min_data.max() * 1.1)
> axes3.set_ylim(numpy.min(min_data), numpy.max(min_data) * 1.1)
> ~~~
......@@ -197,7 +197,7 @@ Solutions to exercises:
> Create a plot showing the standard deviation (`numpy.std`) of the inflammation data for each day across all patients.
>
> ~~~ {.python}
> max_plot = matplotlib.pyplot.plot(data.std(axis=0))
> max_plot = matplotlib.pyplot.plot(numpy.std(data, axis=0))
> matplotlib.pyplot.show()
> ~~~
......@@ -220,13 +220,13 @@ Solutions to exercises:
> axes3 = fig.add_subplot(3, 1, 3)
>
> axes1.set_ylabel('average')
> axes1.plot(data.mean(axis=0))
> axes1.plot(numpy.mean(data, axis=0))
>
> axes2.set_ylabel('max')
> axes2.plot(data.max(axis=0))
> axes2.plot(numpy.max(data, axis=0))
>
> axes3.set_ylabel('min')
> axes3.plot(data.min(axis=0))
> axes3.plot(numpy.min(data, axis=0))
>
> fig.tight_layout()
>
......@@ -513,8 +513,8 @@ Solutions to exercises:
>
> ~~~ {.python}
> def rescale(input_array):
> L = input_array.min()
> H = input_array.max()
> L = numpy.min(input_array)
> H = numpy.max(input_array)
> output_array = (input_array - L) / (H - L)
> return output_array
> ~~~
......@@ -534,10 +534,10 @@ Solutions to exercises:
> that 0 corresponds to the minimum and 1 to the maximum value of the input array.
>
> Examples:
> >>> rescale(np.arange(10.0))
> >>> rescale(numpy.arange(10.0))
> array([ 0. , 0.11111111, 0.22222222, 0.33333333, 0.44444444,
> 0.55555556, 0.66666667, 0.77777778, 0.88888889, 1. ])
> >>> rescale(np.linspace(0, 100, 5))
> >>> rescale(numpy.linspace(0, 100, 5))
> array([ 0. , 0.25, 0.5 , 0.75, 1. ])
> '''
> ~~~
......@@ -554,8 +554,8 @@ Solutions to exercises:
> ~~~ {.python}
> def rescale(input_array, low_val=0.0, high_val=1.0):
> '''rescales input array values to lie between low_val and high_val'''
> L = input_array.min()
> H = input_array.max()
> L = numpy.min(input_array)
> H = numpy.max(input_array)
> intermed_array = (input_array - L) / (H - L)
> output_array = intermed_array * (high_val - low_val) + low_val
> return output_array
......@@ -743,7 +743,7 @@ Solutions to exercises:
> # a possible pre-condition:
> assert len(input) > 0, 'List length must be non-zero'
> # a possible post-condition:
> assert input.min() < average < input.max(), 'Average should be between min and max of input values'
> assert numpy.min(input) < average < numpy.max(input), 'Average should be between min and max of input values'
> ~~~
> ## Testing assertions {.challenge}
......@@ -934,11 +934,11 @@ Solutions to exercises:
> data = numpy.loadtxt(filename, delimiter=',')
>
> if action == '-n':
> values = data.min(axis=1)
> values = numpy.min(data, axis=1)
> elif action == '-m':
> values = data.mean(axis=1)
> values = numpy.mean(data, axis=1)
> elif action == '-x':
> values = data.max(axis=1)
> values = numpy.max(data, axis=1)
>
> for m in values:
> print(m)
......@@ -983,11 +983,11 @@ Solutions to exercises:
> data = numpy.loadtxt(filename, delimiter=',')
>
> if action == '--min':
> values = data.min(axis=1)
> values = numpy.min(data, axis=1)
> elif action == '--mean':
> values = data.mean(axis=1)
> values = numpy.mean(data, axis=1)
> elif action == '--max':
> values = data.max(axis=1)
> values = numpy.max(data, axis=1)
>
> for m in values:
> print(m)
......@@ -1027,11 +1027,11 @@ Solutions to exercises:
> data = numpy.loadtxt(filename, delimiter=',')
>
> if action == '--min':
> values = data.min(axis=1)
> values = numpy.min(data, axis=1)
> elif action == '--mean':
> values = data.mean(axis=1)
> values = numpy.mean(data, axis=1)
> elif action == '--max':
> values = data.max(axis=1)
> values = numpy.max(data, axis=1)
>
> for m in values:
> print(m)
......
......@@ -16,8 +16,8 @@ subtitle: Reference
* Use `low:high` to specify a slice that includes the indices from `low` to `high-1`.
* All the indexing and slicing that works on arrays also works on strings.
* Use `# some kind of explanation` to add comments to programs.
* Use `array.mean()`, `array.max()`, and `array.min()` to calculate simple statistics.
* Use `array.mean(axis=0)` or `array.mean(axis=1)` to calculate statistics across the specified axis.
* Use `numpy.mean(array)`, `numpy.max(array)`, and `numpy.min(array)` to calculate simple statistics.
* Use `numpy.mean(array, axis=0)` or `numpy.mean(array, axis=1)` to calculate statistics across the specified axis.
* Use the `pyplot` library from `matplotlib` for creating simple visualizations.