<jupyter_start><jupyter_text>Python in a Nutshell was started by <NAME> is licensed under a Creative Commons Attribution 4.0 International License.Based on a work at https://github.com/mathcoding/opt4ds.  1. Python in a NutshellIn this notebook, we will review the basic syntax of Python. This tutorial is targeted to students that have little or no knowledge about Python, but have basic programming experience with other programming languages such as Java, Matlab or ANSI C. Playing with this notebookFor an overview on how to use the Colab Notebooks, we recommend this [video tutorial](https://www.youtube.com/watch?v=inN8seMm7UI).We recommend to try the code snippets and to try to modify the lines of code to better understand how the Python interpreter executes each command.  1.1 Basic Data TypesLet us start by considering the basic data types used in almost every useful program: *numbers*, *logical values*, and *strings*.   1.1.1 NumbersLook at the following code, where we are defining a program variable named `a` to which we assign the value 1. To execute the code either click the "play" icon or press CTRL+ENTER.<jupyter_code>a = 1
print(a)<jupyter_output><empty_output><jupyter_text>In order to check the type of the variable, you can use the `type` function:<jupyter_code>type(a)<jupyter_output><empty_output><jupyter_text>By default, the notebook interpreter running a single line of code tries to print a value resulting from evaluating an expression. Note that `a = 1` is an assignment operation, which does not produce a result, and hence the interpreter did not print anything. While `type(a)` is a function invocation that returns the type of the variable `a`, thus printing its value on the screen.> Please, take the good habit of reading the documentation. You can have access to the documentation of a function by typing '?' followed by the name of the module followed by the function: eg, `?type`. The function `type` is fully described online a this link: [type](https://docs.python.org/3/library/functions.htmltype)Consider the following two lines:<jupyter_code>a = 1.0
print(type(a))<jupyter_output><empty_output><jupyter_text>In this case, the notebook interpreter has recognized at runtime that `1.0` is a floating number, and, as a consequence, the type of `a` is bind to `float`. The Python float is equivalent to a `double` of the C programming language and it has the same precision than the floating numbers in Matlab (i.e., 64 bits).Note that Python makes also dynamically type inference.<jupyter_code>a = 1.0
b = 1
c = b + a
type(c)<jupyter_output><empty_output><jupyter_text>In Python, we have all the basic arithmetic operators:<jupyter_code>a = (3*4)+7//2*7%2
print(a)<jupyter_output><empty_output><jupyter_text>Be careful about the `^` operator, which it is not what you likely expect.<jupyter_code>2^3<jupyter_output><empty_output><jupyter_text>The `^` operator implements the bit-wise xor logical operator.What does the `//` operator do compared to `/`, and the operator `**` compared to `*`?  1.1.2 Logical valuesThe two logical values are denoted by the reserved keywords `True` and `False`, which are of type `bool`. The logical operators are written in plain English: `and`, `or`, `not`. For example:<jupyter_code>a = True
b = False
a and b
a or not b
not a or b
type(a)<jupyter_output><empty_output><jupyter_text>Note that we have reused *on the fly* the variable named `a` several times, dynamically changing its type more than once. You could not do that with C or Java, which contrary to Python are *statically typed* programming language. Types prevent bugs, hence be careful in Python! It is possible to use [*variable type annotations*](https://docs.python.org/3/library/typing.html) to provide *type hints* that can be used to check that types remain static using checkers like [mypy](http://mypy-lang.org/) or IDEs.     1.1.3 StringsAnother very useful data type is **string**, which is nothing else that an ordered sequence of characters.Example:<jupyter_code>a = "COVID-19"
print(a)
a
type(a)<jupyter_output><empty_output><jupyter_text>The type of a string is denoted by `str`. The Python string have several useful builtin functions, as for instances:<jupyter_code>print(a.lower(), a.split('-'))<jupyter_output><empty_output><jupyter_text>First we have converted a string into lower case with `lower()` function, and then we have split the string using as delimiter the string `-`.  1.2 Data StructuresPython has three powerful builtin data-types to store aggregate data (click on each word to get the online documentation):1. [`tuple`](https://docs.python.org/3/library/functions.htmlfunc-tuple)2. [`list`](https://docs.python.org/3/library/functions.htmlfunc-list)3. [`dictionary`](https://docs.python.org/3/library/functions.htmlfunc-dict)Let's see these three data types in action.  1.2.1 TupleA **tuple** is an ordered sequence of elements. Each element can be of a different type.<jupyter_code>a = 1, 2 
print(a, type(a))<jupyter_output><empty_output><jupyter_text>Above, we have a tuple of two elements (i.e., a **pair**). We can use an *unfolding* operation to access the elements of a tuple:<jupyter_code>b, c = a
print(b, c)<jupyter_output><empty_output><jupyter_text>We can use the *indexing* operator to access a single element of a tuple (with indices starting from 0, and not from 1):<jupyter_code>print(a[0], a[1])<jupyter_output><empty_output><jupyter_text>A tuple is usually defined using round parentheses and can have arbitrary length.<jupyter_code>eg_tuple = ("contrario", 3, 2.0, 1, 0, -1)
print("tupla:", eg_tuple, "lunghezza:", len(eg_tuple))
print("tipi:", type(eg_tuple), type(eg_tuple[0]), type(eg_tuple[1]), type(eg_tuple[2]))<jupyter_output><empty_output><jupyter_text>Note however that tuple are **reading-only** (aka **immutable**) data types, that is we cannot modify an element of a tuple:<jupyter_code>a[1] = 4<jupyter_output><empty_output><jupyter_text>If you need to store a **mutable** ordered sequence of data, you can use a `list` instead of a `tuple`, as we show next.  1.2.2 ListA list is mutable ordered sequence of data. It can be used in place of tuples, but with the possibility of adding new elements to the list or to change the value of a single element.<jupyter_code>Ls = [1, 2, 2.5, 2.75, 3, "Star!"]
print(Ls)
print(Ls[2], Ls[-1])
# the negative indices start from the last position
Ls[4] = 2.99
print(Ls)
Ls.append('!!!')
Ls
# To get the length of a list:
print(len(Ls))<jupyter_output><empty_output><jupyter_text>We can sum up two lists:<jupyter_code>As = [1,2,3]
Bs = [4,5,6]
Cs = As + Bs
print(Cs)
Ds = As + As
print(Ds)<jupyter_output><empty_output><jupyter_text>Note that the `addition` operator is implemented as a function that returns a new list containing in order, first, every element of the first list and, later, all those of the second list (concatenation). Using the `remove` function, we can remove from a list the first occurrence of an element.<jupyter_code>Ds.remove(3)
print(Ds)<jupyter_output><empty_output><jupyter_text>Since *lists* are ordered sequences of data, we can **index** every single element, and we can **slice** a subset of the elements using the slicing operator, which has the following syntax:```Ls[startIndex:endIndex]```and is used as follows:<jupyter_code>Ls = [9,8,7,6,5,4,3,2,1]
print(Ls[4:7])<jupyter_output><empty_output><jupyter_text>The command has printed the elements from position 4 (indexing start from position 0) to position 6 (just before 7).The slicing operator has default values equal to `startIndex=0` and `endIndex= - 1`.<jupyter_code>print(Ls[:4])  # startIndex is omitted hence is equal to 0
print(Ls[4:])  # endIndex is omitted hence is equal to len(Ls)-1
print(Ls[:])<jupyter_output><empty_output><jupyter_text>Lists are ordered sequence of data with no restrictions on the type that each single element can take. The type of each element is determined at runtime by the Python interpreter.<jupyter_code>Ls = [1, 2.0, [1,"tre"]]<jupyter_output><empty_output><jupyter_text>In this case, we have a list of three elements: an integer, a float, and a list of two elements (an integer and a string). By using lists, we can represent a matrix $A\in \mathbb{R}^{m\times n}$ as a list of lists of numbers.<jupyter_code>I = [[1,0,0,0], [0,1,0,0],[0,0,1,0],[0,0,0,1]]   # Identity matrix
print(I)
type(I)
type(I[3])<jupyter_output><empty_output><jupyter_text>This way of representing lists has nothing to do with the approach used by Matlab or R to store matrices. Experiment adding a scalar to a list or multiplying a list by a scalar. What is the result? Is it the same you would have expected from linear algebra? We will see later in this course a different method based on the [numpy](https://numpy.org/) library for storing vectors and matrices.  1.2.3 DictionariesThe `dictionary` data type is one of the features which has contributed the most to the success of Python.Dictionaries are unordered sequences of pairs of data, where the first element of each pair is called the `key` and the second element is called the `value`. Conceptually, dictionaries are the equivalent of text dictionary: we use the key (a word) to quickly look for the corresponding value (the formal definition of the key/word).An intuitive way to think of a dictionary is that of a *kind-of-list* that can be indexed by any read-only (immutable) data type (e.g., `int, float, tuple, string`).<jupyter_code>Ds = dict()  # Create en empty dictionary
print(Ds, type(Ds))
Ds["star"] = "stella"
Ds["war"] = "guerra"
print(Ds)
Ds["luke"] = "luca"
print(Ds)<jupyter_output><empty_output><jupyter_text>Notice that we have something very similar to a list of pairs.<jupyter_code>print(Ds["luke"])
print(len(Ds))  # Number of pairs (key, value) contained into the dictionary<jupyter_output><empty_output><jupyter_text>Sometime it might be useful to build a dictionary starting from two different lists (but which have the same length). The first is the list of *keys*, the second is the list of *value*. Look at the following snippets:<jupyter_code>As = ["a", "b", "c", "d"]
Bs = [1, 2, 3, 4]
Ds = dict(zip(As, Bs))
print(Ds)<jupyter_output><empty_output><jupyter_text>In this example, we have used the builtin function [zip](https://docs.python.org/3/library/functions.htmlzip). If it is unclear how the function **zip** works, try to understand it by inspection running the following line<jupyter_code>Ps = list(zip(As, Bs))
print(Ps)<jupyter_output><empty_output><jupyter_text>With the function zip, we have constructed a list of pairs, by *zipping* the elements of the two lists in consecutive pairs.  1.3 Flow ControlsIn the previous cells, we have used only small blocks of code with the purpose of showing basic data structures. When writing a simple function or a larger program it is necessary to write statements that control the flow of execution. We review here the syntax for the flow control execution available in Python.  1.3.1 ConditionalsThe first flow control statement is the `if ... then ... else` construct, which has the following syntax:```if :   Either the first block of code, e.g.:  print('True')else:   Or execute the second block of code, e.g.:  print('False')```The logical condition can be any logical operation (e.g., $a > 3$) or a function return a logical value. The `else` condition is optional and it can be omitted.For instance, if we have to check whether a number is even, we can write:<jupyter_code>a = 14
if a % 2 == 0:
  print("Number a={} is EVEN".format(a))
else:
  print("Number a={} is ODD".format(a))<jupyter_output><empty_output><jupyter_text>Note: from Python 3.8 instead of `format` you can use *Formatted string literals* to format the output.```pythonvalue=3.14159print(f"Number a={value:{7}.{2}} is a {type(value)}")``` If we need to control several alternatives, we can use one (or more) `elif` conditions.<jupyter_code>if a > 0:
  print("positive")
elif a < 0:
  print("negative")
else:
  print("zero")<jupyter_output><empty_output><jupyter_text>1.3.2 For LoopsGiven a sequence of elements, such as tuples, lists, and dictionaries, we often need to iterate over an element at the time.The Python syntax for looping trough the elements of a list is```for  in :   Block of code, e.g.  print("single loop")```The simplest task we can think of is to print an element at the time the elements of a given list.<jupyter_code>Ls = [1, 2.0, "tre", (1,2), ["a,b"], {"x": 1, "y":1}]
for e in Ls:
  print(type(e), "  ", e)<jupyter_output><empty_output><jupyter_text>We can loop over any data which represent a sequence, as shown next.<jupyter_code>Ts = ("a","b","c")
for t in Ts:
  print(t)
virus = "COVID-19"
for c in virus:
  print(c)
print(Ds)
for key in Ds:
  print("key = {}, value = {}".format(key, Ds[key]))<jupyter_output><empty_output><jupyter_text>There are a few cases where it can be useful to know the index of a given element within an ordered sequence. In this case, we can use the [`enumerate`](https://docs.python.org/3/library/functions.htmlenumerate) builtin function as follows.<jupyter_code>for i, e in enumerate(Ls):
  print("index = {}, element = {}".format(i,e))<jupyter_output><empty_output><jupyter_text>You can see that a list is like a special kind of dictionary where the keys are a sequence of increasing integers starting from zero.  1.3.3 While LoopsThe syntax for the `while` loop is pretty standard, and once you get used to the Python syntax style, it should be also intuitive:```while :  ```Which can be used as follows.<jupyter_code>counter = 3
while counter <= 30:
  print(counter)
  counter += 3<jupyter_output><empty_output><jupyter_text>As usual with Python:> PAY ATTENTION TO THE INDENTATION SPACE AT THE BEGINNING OF A LINE  1.3.4 ExceptionsAn [exception](https://docs.python.org/3/tutorial/errors.html) breaks the normal execution flow of a program in exceptional cases, such as, when encountering unplanned errors.By running this notebook by your own, you have likely already encountered exceptions, as a way of reporting errors from the Python interpreter.<jupyter_code>p = (1,2)
p[0] = "a"<jupyter_output><empty_output><jupyter_text>In this case, the exception was **raised** by some internal function of the Python interpreter. In order to control the execution flow of a program, the exceptions can be **caught** using the following syntax:<jupyter_code>try:
  # Block of code that might be raise an exception
  p = (1,2)
  p[0] = "a"
except Exception as e:
  # Block of code executed in case of an exception
  print(e)
  print(type(e))<jupyter_output><empty_output><jupyter_text>There are several different types of exceptions in Python, and you should familiarize with those types, in order to identify the errors in yours programs. A complete list of exceptions is available on the official documentation: [Builtin Exceptions](https://docs.python.org/3/library/exceptions.htmlbltin-exceptions). When you are writing your own function (see next section), for example for parsing a text file, you might need to raise an exception whenever an input file does not meet the given specification. To raise an exception, the Python syntax is as follow:```  raise Exception("")```For instance, if you would like to handle only positive numbers, you can write the following code:<jupyter_code># NOTE: The purpose of this snippet is only of showing how to raise an exception
a = -1
if a < 0:
  raise Exception("I deal only with positive numbers!")<jupyter_output><empty_output><jupyter_text>At this point, as a side effect, it should be also easier to understand the errors encountered when trying to execute a Python program.  1.4 FunctionsIn all the previous paragraphs, we have illustrated the basic data type and the fundamental statements for controlling the execution flow of a program.In this section, we review how we can organize our code in small blocks, each block corresponding to a specific function or procedure.  1.4.1 Pure FunctionsYou can think of computer programming *pure* functions as they were mathematical functions, where the output depends only on the input (we have zero side effects). Consider the function of computing hypotenuse of a triangle: $c^2 = a^2 + b^2$. We can write a small **function** that compute the square of the hypotenuse length $c$, given the two values of $a$ and $b$:<jupyter_code>def SquareHypotenuse(a, b):
  c = a**2 + b**2
  return c<jupyter_output><empty_output><jupyter_text>In the previous example, we have used the following syntax:```def ():  ```There are a few points that must be marked:1. The keyword `def` is reserved and is used to define a function.2. **FunctionName** is any name you want to use to define your function.3. The round parenthesis and the colon are mandatory.4. The **list of formal parameters** is up to you, and it can be of any (reasonable) size.5. **MOST IMPORTANTLY:** the indentations after the **def** keyword are part of the **semantic** of the construct, and denote the scope of the function.6. **Function DEFINITION** is different from **Function INVOCATION**. With the previous block, we have defined the function **Hypotenuse**, but we have not yet passed any actual parameters to the function, which could trigger the computation of the *c* value.If we want to compute the square value of the hypotenuse of a triangle with base equal to 4 and height equal to3, you have to write:<jupyter_code>b, h = 4, 3  # We are defining and unfolding the pair (4, 5) on the fly
hyp = SquareHypotenuse(b, h)
print(hyp)<jupyter_output><empty_output><jupyter_text>Indeed, a large number of mathematical functions are already implemented in Python. For instance, all the basic mathematical functions are implemented in the [math](https://docs.python.org/3/library/math.html?highlight=mathmodule-math) library.If you want to import a single function (e.g., the square root function [`sqrt()`](https://docs.python.org/3/library/math.html?highlight=mathmath.sqrt), you can write:<jupyter_code>from math import sqrt
print(sqrt(2))<jupyter_output><empty_output><jupyter_text>At this point, it should be easy to write a function that compute the actual value of the hypotenuse (and not its square value), by using a function *composition*.<jupyter_code>def Hypotenuse(a, b):
  return sqrt(SquareHypotenuse(a,b))
print(Hypotenuse(4,3))<jupyter_output><empty_output><jupyter_text>Indeed, function can be chained several times.<jupyter_code>c = Hypotenuse(Hypotenuse(3,4), 5+2*SquareHypotenuse(2, sqrt(4)))
print(c)<jupyter_output><empty_output><jupyter_text>**QUESTIONS:** Can you figure out the order in which the previous expressions are evaluated in order to compute `c`? In a function definition, we can also set a default value for any formal parameter, using the following syntax.<jupyter_code>from math import pow
def Power(x, n=3):  # The value 3 is the default for formal parameter "n"
  return x**n

print(Power(2,5))
print(Power(3))  # Here, the interpreter uses the default value for the second parameter<jupyter_output><empty_output><jupyter_text>1.4.2 ProceduresProcedure are like functions, but they do not explicitly return any value.<jupyter_code>def Print(a, b, c=77):
  print("a = {}, b = {}, c = {}".format(a, b, c))
Print(2,3)<jupyter_output><empty_output><jupyter_text>A typical error is to use a procedure as it were a pure function. The syntax is correct, but it is a misuse of the procedure.<jupyter_code>d = Print(a, b)
print(d)<jupyter_output><empty_output><jupyter_text>Notice that there is not `return` keyword in the procedure `Print(a, b, c)`. However, in this case, it is like there was a `return None` statement at the end of the procedure definition. For this reason, procedure are also called *void functions*.<jupyter_code>def Print(a, b, c=77):
  print("a = {}, b = {}, c = {}".format(a, b, c))
  return None
print(Print(1,1,2))
None<jupyter_output><empty_output><jupyter_text>1.4.3 Lambda FunctionsIn some case, we can use anonymous functions, called `lambda functions`. They are mostly used when we need to pass a function as a actual parameter of a formal parameter (see next section).We can use lambda functions also for one-liner definition of functions.<jupyter_code>F = lambda x: x*x
print(F(2))<jupyter_output><empty_output><jupyter_text>1.4.4 Map and FilterAmong the python builtin functions, we like to stress the importance of [map](https://docs.python.org/3/library/functions.htmlmap) and [filter](https://docs.python.org/3/library/functions.htmlfilter).They are high-order functions, that is functions that take as input other functions.The `map` function takes as input a function *f* and any data type that is a sequence (an **iterable** to be precise), and it applies the function *f* to every element of the sequence.The `filter` function takes as input a function *f* and any data type that is a sequence, and it returns a new sequence containing only the elements for which the function *f* returned `True`.For example:<jupyter_code>Ls = [1, 2, 3, 4, 5]   # String, list, tuple, dictionaries are iterable
for e in map(lambda x: x*x, Ls):
  print(e)
Ls = [1, 2, 3, 4, 5]   # String, list, tuple, dictionaries are iterable
for e in filter(lambda x: x%2 == 0, Ls):
  print(e)<jupyter_output><empty_output><jupyter_text>In case of doubts, you can always use the online documentation, via the `help()` function.<jupyter_code>#help(map)
#help(filter)<jupyter_output><empty_output><jupyter_text>Note that you can perform the same tasks with list comprehensions. Compare the following:<jupyter_code>Ls = [1, 2, 3, 4, 5]   
print( list(map(lambda x: x*x, Ls)) )
print( [x*x for x in Ls] )
Ls = [1, 2, 3, 4, 5]   # String, list, tuple, dictionaries are iterable
print( list( filter(lambda x: x%2 == 0, Ls) ) )
print( [x for x in Ls if x%2==0] )<jupyter_output><empty_output><jupyter_text>1.5 Classes and ObjectsIn Sections 1.1 and 1.2 we have seen old plain data types (numbers and strings) and structured data types (tuple, list, dictionaries).In this section, we show how we can define new data types, which can be composed of other data.  1.5.1 SyntaxThe minimal syntax to define a new type of data is the following:```class (object):  def __init__(self):    self. =     ...  def __str__(self):    s =     return s  def __repr__(self):   This is recommended    return self.__str__()```The term *class* is here a synonym of **type**, and it is used to inform the interpreter that in the following lines of code we are declaring a new data type. The basic methods that every class should have are:1. The constructor method `__init__(self)`, which is used to initialize the internal data of the class. The method can take on or more parameters in input, as we will see in the next example.2. The string conversion method `__str__(self)` that is use to return a string which gives a human readable representation of the actual values of the class attributes.3. The method `__repr__(self)` is used here as a proxy of `__str__(self)`. It is convenient for printing list of user defined objects.  1.5.2 Example: Discrete Distributions in $\mathbb{R}^3$We have to represent a discrete measure $\mu(X)$ with $X \subseteq \mathbb{R}^3$. Suppose we are given a finite number of $n$ support points $\mathbf{x}_i \subseteq \mathbb{R}^3$ of weight $w_i \in \mathbb{R}$, then we can define the discrete measure:$$\delta = \sum_{i = 1}^n w_i \delta(\mathbf{x}_i)$$**QUESTION:** How can we define a class for the type **point in $\mathbb{R}^3$**?**QUESTION:** How can we define a class for the type **discrete measure**? A point of $\mathbb{R}^3$ can be represented with a tuple of 3 floats with the following class.<jupyter_code>class Point3D(object):
  def __init__(self, x1=0, x2=0, x3=0):
    self.x1 = x1
    self.x2 = x2
    self.x3 = x3
  def __str__(self):
    return "( x1 = {}, x2 = {}, x3 = {} )".format(self.x1, self.x2, self.x3)
  def __repr__(self):
    return self.__str__()<jupyter_output><empty_output><jupyter_text>So far, we have just defined a new data type, that is, a new class. Until here, it is like we were declaring a new function. However, as with function we are interested in **applying** a function to given values to get the result value, with a class we are interested in **applying** the class to data to get an **instance** of a class, that is an **object** of the type defined by the corresponding **class**.To get three objects of type `Point3D`, we can run the following code.<jupyter_code>P1 = Point3D()
P2 = Point3D(1, 1, 1)
P3 = Point3D(x3 = -1)
print(P1)
print(P2)
print(P3)<jupyter_output><empty_output><jupyter_text>Suppose we want to create a list of 10 random points where every coordinates takes value in the interval $[0..1]$. We can use the Python [random library](https://docs.python.org/3/library/random.html) as follows.<jupyter_code>from random import random, uniform, seed
Xs = []
for _ in range(10):
  Xs.append(Point3D(random(), random(), random()))
print(Xs)
for p in Xs:
  print(p)<jupyter_output><empty_output><jupyter_text>If we want to generate 10 random weights $w_i$ that sum up to 1, we can run the following code.<jupyter_code>def RandomWeights(n, a=0, b=100):
  Ws = []
  # Generate random points with given Uniform distributions
  for _ in range(n): # the underscore _ indicates that we are not capturing the values 
    Ws.append(uniform(a, b))
  # Normalize vector
  tot = sum(Ws)
  for i, w in enumerate(Ws):
    Ws[i] = w/tot
  return Ws
Ws = RandomWeights(10)
print(Ws)
for w in Ws:
  print(round(w, 3))
sum(Ws)<jupyter_output><empty_output><jupyter_text>**QUESTION:** How can we compose all the code that we have just seen to define a class of type **DiscreteMeasure**?A possibility is the following.<jupyter_code>class DiscreteMeasure(object):
  def __init__(self, n, a=0, b=100):
    # Sample 1n random points in R^3
    self.Xs = [Point3D(uniform(a, b), uniform(a, b), uniform(a, b)) for _ in range(n)]
    # Sample a random point of the simplex of dimension n
    Ws = []
    for _ in range(n):
      Ws.append(random())
    tot = sum(Ws)
    for i, w in enumerate(Ws):
      Ws[i] = w/tot
    self.Ws = Ws

  def __str__(self):
    s = "["
    for i, (x, w) in enumerate(zip(self.Xs, self.Ws)):
      s += "w_{} ={}, p_{} = {},\n".format(i, str(w), i, str(x))
    s += ']'
    return s.replace(',\n]',']')
delta = DiscreteMeasure(5)
print(delta)<jupyter_output><empty_output><jupyter_text>1.6 PlottingFinally, we want to plot our discrete distribution on $\mathbb{R}^3$.For this task, we will use [Matplotlib](https://matplotlib.org/). We show here directly how to represent our discrete distribution using a [3D scatterplot](https://matplotlib.org/3.2.0/gallery/mplot3d/scatter3d.html).<jupyter_code># Two random discrete distributions
mu = DiscreteMeasure(40, 10, 30)
nu = DiscreteMeasure(100, 25, 50)

# Import of plot libraries
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# Create a figure
fig = plt.figure(figsize=(8,6))
ax = Axes3D(fig)
# Scatter plot of first distribution
ax.scatter([p.x1 for p in mu.Xs], 
           [p.x2 for p in mu.Xs],
           [p.x3 for p in mu.Xs],
           s=[(2000*w) for w in mu.Ws],  # Dot size linear with weights value
           color='darkblue', alpha=0.3,  # Set color and transparency
           marker='o')
# Scatter plot of second distribution
ax.scatter([p.x1 for p in nu.Xs], 
           [p.x2 for p in nu.Xs],
           [p.x3 for p in nu.Xs], 
           s=[(2000*w) for w in nu.Ws],  # Dot size linear with weights value
           color='red', alpha=0.3,       # Set color and transparency
           marker='o')
# Labels for axis
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
# Visualize the plot
plt.show()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1045475855602438, 'fraction_numerical': 0.01478596415305276, 'mean_word_length': 4.973718224903059, 'pattern_counts': {'":': 2, '<': 217, '<?xml version=': 0, '>': 218, 'https://': 19, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '53', 'dir': 'jupyter-structured-clean-dedup', 'id': '72337', 'license': 'CC0-1.0', 'n_tokens_mistral': 8784, 'n_tokens_neox': 8424, 'n_words': 3605, 'path': 'assets/Python_in_a_Nutshell.ipynb', 'repo_name': 'DM561/dm561.github.io', 'size': '27739'}