<jupyter_start><jupyter_text>"Geo Data Science with Python"  Notebook Lesson 2  Basic Object Types: Numbers, Booleans and StringsThis lesson discusses more details of the basic Python object types **Numbers**, **Booleans** and **Strings**. In the aftermath, strings will be further deepened by the subsequent reading material *String Fundamentals* (Lutz, 2013), available in the course's reading list on canvas. SourcesSome elements of this notebook source from Chapter 5 and Chapter 7 of Lutz (2013).---  Part A: Numeric Object Types in PythonEffective data-driven science and computation requires understanding how data is stored and manipulated (VanderPlas, 2016).Most of Python's number types are typical and will seem familiar if you have used other programming languages before. However, numbers are not really a single object type but rather a category. Python supports the usual numeric types (integers and floating point) as well as literals for creating numbers, expressions for processing them and some built-in functions and modules. Python also allows to write integers using hexadecimal, octal and binary literals; offers complex number types Python and allows integers to have unlimited precision - they can grow to have as many ditigs as your memory space allows. Lutz (2013) gives the following overview for numeric object types in Python:Table 1: *Numeric literals and constructors (Lutz, 2013, Table 5.1).* Built-in numbers are enough to represent most numeric quantities - from your age to your bank balance - but more types are available from external (third-party) Python packages.Below we briefly introduce the most important ones for this course. These are integer and floating numbers as well as Boolean types. The latter allows for logic operations.  IntegersIntegers are written as strings of decimal digits. These numbers have no fractional component. The size of integer numbers is only limited by your computer's memory. Python's basic number types support the normal mathematical operations, like addition and substraction with the plus and minus signs ```+/-```, multiplication with the star sign ```*```, and two stars are used for exponentiation ```**```. Try to edit and execute the following example performing substractions, multiplications and divisions. What happens? Are the results of all of these operations also of type integer?<jupyter_code>123 + 222
type(123 + 222)<jupyter_output><empty_output><jupyter_text>Indeed, most mathematical operations involving two integer numbers, will also return an integer number. However, divisions do not return an integer number. This is holds even for divisions without remainder. Instead, thanks to the dynamic typing in Python, we get a floating point number:<jupyter_code>type(4/2)<jupyter_output><empty_output><jupyter_text>In Python 3 (which we are using here, as you can see from the Kernel type at the top right), if you want to specifically perform an integer division, you have to mark this by using a double division symbol: ```//```.<jupyter_code>4//2         # integer division
type(4//2)<jupyter_output><empty_output><jupyter_text>Just as a side note: The integer division ```//``` in Python 3 is actually a floor division, provided by the Python module math. We will discuss Python modules, at a later point in the course.<jupyter_code>import math
math.floor(123/222)<jupyter_output><empty_output><jupyter_text>Floating-point NumbersFloating-point numbers have a fractional component. A decimal point and/or an optional signed exponent introduced by an ```e``` or ```E``` and followed by an optional sign are used to write floating-point numbers.<jupyter_code>type(3.14)    # literal for a floating-point number
314e-2        # literal for a floating-point number in scientific notation<jupyter_output><empty_output><jupyter_text>Floating-point numbers are implemented as C "doubles", therefore they get as much precision as the C compiler used to build the Python interpreter gives to doubles (usually that is 15 decimal digits of precision). For more precision, external Python packages have to be used. In addition, Python 3 automatically handles a user-friendly output of floating-point numbers. For example, if we define the mathematical constant π to a numeric object, the unformatted output on screen will have the same length.<jupyter_code>pi_approximate = 3.14
pi_accurate = 3.141592653589793
print(pi_approximate)
print(pi_accurate)<jupyter_output>3.14
3.141592653589793<jupyter_text>However, when printing the variable to the screen, you can also change the precision of the output, by using the modulus operator ```%```. If you want to print out 4 digits after the comma, indicate this with ```%.4f``` in the following way:<jupyter_code>print('%.4f'%pi_accurate)   # formated screen output using print() for floating-point numbers<jupyter_output>3.1416<jupyter_text>Alternatively, the output can be formatted in scientific notation or as ingeter number, thought the indicators ```e``` and ```i```:<jupyter_code>print('%.4e'%pi_accurate)   # formated screen output using print() for numbers in scientific notation
print('%i'%pi_accurate)     # formated screen output using print() for integer numbers<jupyter_output>3<jupyter_text>We will discuss further details of formatted output using the print function, further below in the section about strings. Furthermore, since variables are strongly typed in Python, you cannot change their type, but you can change the output to the screen or assign a changed output to another variable.For example, the function ```int()``` truncates a floating-point number into an integer number:<jupyter_code>int(3.141)<jupyter_output><empty_output><jupyter_text>And the function ```float()``` does the opposite:<jupyter_code>float(3)<jupyter_output><empty_output><jupyter_text>Take notice what happens, if an operation is performed that involves both number types floating-point and integer. In that case, before the Python intepreter performs the operation, it converts the elements of the operation up to the most complicated type. Hence, the output object type of a mathematical operation that includes integer and floating-point numbers will be of floating-point type:<jupyter_code>type(40 + 3.141)<jupyter_output><empty_output><jupyter_text>Built-in Numeric ToolsWe have already mentioned some basic mathematic operations. Now let's discuss more expressions available for processing numeric object types and some built-in functions and modules. We will meet many of these as we go along. *Expression operators:*```+```, ```-```, ```/```, ```*```, ```**```, ```%```, etc.Expressions operators are used for mathematical operations between two numbers. Above listed are the operands of an addition, substraction, division, multiplication, exponent, and modulus. Go to this website to find a comprehensive list of expression operators: https://www.tutorialspoint.com/python/python_basic_operators.htmIt is important to keep in mind that:* Mixed operators follow operator precedence (similar to mathematical operations: multiplications precede additions, hence, ```5+1*10=50```. For a full list of precedence orders see table 6.16 in the Python documentation: https://docs.python.org/3/reference/expressions.html)* Parantheses group subexpressions (exactly like in mathematics: ```(5+1)*10=60``` but ```5+(1*10)=50```)* Mixed types are converted up (as already discussed for the last example in the section about floating-point numbers) *Built-in functions:*Python has some built-in functions and some of them are useful for basic numeric processing. Examples are:```pow()```, ```abs()```, ```round()```, ```int()```, ```hex()```, ```bin()```, etc. The documentation pages of the Python language provides a comprehensive list: https://docs.python.org/3/library/functions.html *Utility modules:*The packages (modules) ```random``` and ```math``` provide further functions useful for mathematical operations. The documentation pages of the Python language provides a comprehenisve overview of functions coming with the math module: https://docs.python.org/3/library/math.html Such modules have to be imported before first, and then functions in that module can be accessed by combining their names with a literal ```.``` (similar to the example above using the ```math``` function ```floor()``` ):<jupyter_code>import math
math.floor(3.14)<jupyter_output><empty_output><jupyter_text>The ```math``` module contains more advanced numeric tools as functions. Conveniently, the math module comes also with some mathematical constants and trigonometric functions:<jupyter_code>math.sqrt(99)
math.pi, math.e    # returns the mathematical constants pi and euler's number e
math.sin(math.pi/2)<jupyter_output><empty_output><jupyter_text>After importing the ```random``` module, you can perform random-number generation ...<jupyter_code>import random
random.random()<jupyter_output><empty_output><jupyter_text>... and random selections (here, from a Python *list* coded in square brackets - an object type to be indroduced later in this course module):<jupyter_code>random.choice([1,2,3,4]) # choice([L]) chooses a random element from L<jupyter_output><empty_output><jupyter_text>Go ahead and use the following code cell to try some of the functions and modules in the examples and/or links above (but be aware that some in the links listed functions request more advanced object types, that we haven't discussed yet).<jupyter_code>math.ceil(3.14)      # ceil(x) returns the smallest integer >= x.<jupyter_output><empty_output><jupyter_text>And of course, you can do all of discussed and listed numerical operations with variables that have been assigned with a numerical values.<jupyter_code>a = math.pi
b = math.sin(math.pi*5/4)
print(b)<jupyter_output>-0.7071067811865475<jupyter_text>Using variable of numeric object type with expressions, the following has to be kept in mind for Python:* Variables are created when they are first assigned values.* Variables are replaced with their values when used in expressions.* Variables must be assigned before they can be used in expressions.* Variables refer to objects and are never declared ahead of time.Now, you have gained the most important knowledge to use and process variables of numeric object type in Python. For even more complex numerical operations, especially involving data tables, one has to refer to separate, external Python packages. We will discuss modules in general and external Python packages in specific during a later course module.   Python HELP???!!!If you ever wonder what a function's function is without starting any literature or internet search, you may always consult the very useful built-in function ```help()```, through which you can request the manual entry for any function:<jupyter_code>help(abs)<jupyter_output>Help on built-in function abs in module builtins:

abs(x, /)
    Return the absolute value of the argument.<jupyter_text>The returned text delivers information about syntax and semantics of the function. This work also for functions of imported modules:<jupyter_code>help(math.ceil)<jupyter_output>Help on built-in function ceil in module math:

ceil(x, /)
    Return the ceiling of x as an Integral.
    
    This is the smallest integer >= x.<jupyter_text>Part B: Boolean Types: Truth Values, Comparisons & TestsPython's Boolean type and its operators are a bit different from their counterparts in languages like C. In Python, the Boolean type, ```bool```, is numeric in nature because its two values, ```True``` and ```False```, are basically custom versions of 1 and 0. Also Boolean values ```True``` or ```False``` are treated as numeric *constants* in Python (see the Table 1) and their Boolean object type (```bool```) is actually a subtype (subclass) of integers (```int```).<jupyter_code>type(True)<jupyter_output><empty_output><jupyter_text>Let's look at some examples to understand how Boolean types and their operators function in Python. Boolean Truth ValuesIn Python all objects have an inherent *Boolean* true or false value. We can define:* Any nonzero number or nonempty object is true.* Zero numbers, empty objects, and a special object ```None``` are considered false.The built-in function ```bool()```, which tests the Boolean value of an argument, is available to request this inherent value for any variable. For example:<jupyter_code>a = 0
b = None
c = 10.0
bool(a), bool(b), bool(c)<jupyter_output><empty_output><jupyter_text>Because of Python's customization of the Boolean type, the output of Boolean expressions typed at the interactive prompt prints as the words ```True``` and ```False``` instead of the older and less obvious ```1``` and ```0```. Most programmers had been assigning ```True``` and ```False``` to ```1``` and ```0``` anyway. The ```bool``` type simply makes this standard. It's implementation can lead to curious results, though. Because ```True``` is just the integer ```1``` with a custom display format, ```True + 4``` yields integer ```5``` in Python!<jupyter_code>True + 4<jupyter_output><empty_output><jupyter_text>By the way, very much like the Boolean values ```True``` and ```False```, also the value ```None``` is a built-in constant. However the  ```None``` value is special, as it basically sets a variable to an empty value (much like a ```NULL``` pointer in C) and it has it's very separate and unique object type:<jupyter_code>type(None), type(True)<jupyter_output><empty_output><jupyter_text>See the top of this Python documentation page for explanations of the built-in constants: https://docs.python.org/3/library/constants.html  Comparisons & Equality testsAlso comparisons and equality tests return ```True``` or ```False```. Range comparisons can be performed using the expression operators ``````, ```>=```, ```<=```; and equality tests using the expression operators ```==```, ```!=```. For example:<jupyter_code>a < c, a==c, b!=c<jupyter_output><empty_output><jupyter_text>Notice how mixed types are allowed in numeric expressions (only). In the first test above, Python compares an integer and a floating-point number with each other as well as a number with the NoneType.   Boolean TestsBoolean tests use the logical operators ```and``` and ```or``` and they return a true or false operand object. Such Boolean operators combine the results of other tests in richer ways to produce new truth values. For that, revise also the operator precedence ([Table 6.16 of the Python documentation](https://docs.python.org/3/reference/expressions.html)).More formally, there are three Boolean expression operators in Python, which are typed out as workds in Python (in contrast to other languages):* ```X and Y``` Is true if both ```X``` and ```Y``` are true* ```X or Y``` Is true if either ```X``` or ```Y``` is true* ```not X``` Is true if ```X``` is false (the expression returns ```True``` or ```False```)Here, ```X``` and ```Y``` may be any truth value, or any expression that returns a truth value (e.g., an equality test, range comparison, and so on).Keep in mind, that the Boolean ```and``` and ```or``` operators return a true or false object, not the values ```True``` or ```False```. Let's look at a few examples to see how this works. Compare the following comparison:<jupyter_code>1 < 2, 3 < 1<jupyter_output><empty_output><jupyter_text>... with the output of the following Boolean tests:<jupyter_code>1 or 2, 3 or 1
None or 3
0 and 3<jupyter_output><empty_output><jupyter_text>You can see, that ```and``` and ```or``` operators always return an object. Either the object on the *left* side of the operator or the object on the *right*. If we test their results, using the built-in function ```bool()``` they will be as expected (remember, every object is inherently true or false), but we won't get back a simple ```True``` or ```False```.Furthermore, Boolean ```or``` tests are done in a so called *short-circuit evaluations*. This means the interpreter evaluates the operand objects from left to right. Once it finds the first true operand, it terminates (short-circuits) the evaluation of the rest of the expression. After the first true operand was found, the values of further operands in the expression won't be able to change the outcome of an ```or``` test: ```true``` or anything is always true.Similarily, the Python ```and``` operands stop as soon as the result is known. However, in this case Python evaluates the operands from left to right and stops if the left operand is a ```false``` object because it determines the result: false ```and``` anything is always false.The concept of *short-circuit evaluations* has to be known, to predict the exact output of a Boolean test. Below some examples to study:<jupyter_code>True or 20  # Evaluation stops after first True object: result is True
10 or 20    # Evaluation stops after first non-zero object: result is 10
False and 20 # Evaluation stops after first False: result is False
10 and False # Evaluation stops after first False: result is False
10 and 20   # Evaluation continues until last object: results is 20
            # (no zero or false object)
10 and 20 and 30  # Evaluation continues until last object: results is 30<jupyter_output><empty_output><jupyter_text>Chained ComparisonsIn addition to that, Python allows us to chain multiple comparisons together. Chained compariosns are sort of shorthand for larger Boolean expressions. This allows to perform range tests. For instance, the expression ```(a < b < c)``` tests wheter ```b``` is between ```a``` and ```c```; it is equivalent to the Boolean test ```(a < b and b < c)```. But the former is easier on the eyes (and the keyboard).For example:<jupyter_code>a = 20
b = 40
c = 60<jupyter_output><empty_output><jupyter_text>Now compare:<jupyter_code>a < b < c<jupyter_output><empty_output><jupyter_text>with:<jupyter_code>a < b and b < c<jupyter_output><empty_output><jupyter_text>You can build even longer chains or add comparisons into the chained tests.<jupyter_code>1 < 2 < 3 < 4.0 < 5<jupyter_output><empty_output><jupyter_text>But the resulting expressions can become nonintuitive, unless you evaluate them the way Python does. The following, for example, is false just because 1 is not equal to 2:<jupyter_code>1 == 2 < 3    # Same as 1 == 2 and 2 < 3 (not same as False < 3)<jupyter_output><empty_output><jupyter_text>In this example, Python does not compare the ```1 == 2``` expression's ```False``` result to 3. This would technically mean the same as ```0 < 3```, which would be ```True```.  Identity OperatorsLastly, identity operators compare the memory locations of two objects. There are two identity operators: ```is``` and ```is not```.* ```is``` evaluates to true if the variables on either side of the operator point to the same object and false otherwise.* ```is not``` evaluates to false if the variables on either side of the operator point to the same object and true otherwise.For example, remember from the last notebook what we have learned about how Variable names are referenced to objects in Python? From that, it becomes obvious the following identity test has to be true:<jupyter_code>a = 3
b = a
a is b<jupyter_output><empty_output><jupyter_text>And with identity tests, we can also show, that the Boolean "number" ```True``` and the integer number ```1``` are of the same value (both are basically an integer number ```1```), but not of the same object:<jupyter_code>True == 1    # Same value
True is 1    # But a different object<jupyter_output><>:1: SyntaxWarning: "is" with a literal. Did you mean "=="?
<>:1: SyntaxWarning: "is" with a literal. Did you mean "=="?
<ipython-input-48-6e82371b8779>:1: SyntaxWarning: "is" with a literal. Did you mean "=="?
  True is 1    # But a different object<jupyter_text>Boolean Types: SummarySo let's summarize briefly, what we have discussed about Boolean types and operators:* Any nonzero number or nonempty object is true.* Zero numbers, empty objects, and a special object ```None``` are considered false.* Comparisons and equality tests are applied recursively to data structures.* Comparisons, equality tests and identity operators return ```True``` or ```False``` (which are custom versions of 1 and 0)* Boolean ```and``` and ```or``` operators return a true or false operand object.* Boolean operators stop evaluating ("short circuit") as soon as a result is known.Refer back to this website to find a comprehensive list of expression operators, including those for comparisons and equality test as well as logical operators and identity operators: https://www.tutorialspoint.com/python/python_basic_operators.htm  Part C: Strings in Python Strings are used to record both, textual information (your name, for instance) as well as arbritrary collection of bytes (such as image file's contents). They are our first example, of what in Python we call a ***sequence*** - **a positionally ordered collection of other objects**. Sequences maintain a **left-to-right order** among the items they contain: their items are stored and fetched by their relative positions. Strictly speaking, strings are sequences of one-character strings; other, more general sequence types include *lists* and *tuples*, coverd later (Lutz, 2013). But let's first begin with the syntax for generating strings. String LiteralsPython strings are easy to use and several syntax forms can be used to generate them. For example, we can assign the a string "```knight's```" to a variable ```S``` in different ways:<jupyter_code>S1 = 'knight"s'       # single quotes
S2 = "knight's"       # double quotes
S3 = '''knights'''    # triple quotes
S4 = '\nknight\'s'    # escape sequence
print(S1 , S2 , S3 , S4, )<jupyter_output>knight"s knight's knights 
knight's<jupyter_text>Single and double-quote characters are interchangeable and they can be enclosed in either. You can also embed one in the other and vice versa, as seen in the examples above. Triple quotes are an alternative to code entire *block strings*. That is a syntactic convenience for coding mulitiline text data.Escape sequences allow embedding of special characters in string cannot easily be typed on a keyborad. In the string literal, one Backslash ```\``` precedes a character. The character pair is then replaced by a single character to be stored in the string:* ```\n``` stores a newline* ```\t``` stores a horizontal tab* ```\v``` stores a vertical tab* ```\\```,```\'```,```\''``` for special caracters like Backslash, single quotes or double quotes The ```\\``` stores one ```\``` in the string. While the function print replaces the escape characters (see code cell above). However, the interactive echo of the interpreter keeps showing the special characters as escapes:<jupyter_code>S4<jupyter_output><empty_output><jupyter_text>String PropertiesBecause strings are sequences, they support operations that assume a positional ordering among its items. For example, one can request the length of a string with the built-in function ```len()```.  And one can select and print out certain items of a string, or in other words, fetch its components with *indexing* expressions.<jupyter_code>len(S1)   # len returns ength of a string sequence
S1[0]     # returns the first item from the left
S1[1]     # returns the second item from the left<jupyter_output><empty_output><jupyter_text>In Python, indexing is coded as offsets from the front. The first item is at index 0, the second at index 1 and so on. In addition to that, strings allow the following typcial sequence operations.* slicing: general form of indexing - extract an entire section (slice) of a string in a single step* concatenating: joining two strings into a new string* repeating: making a new string by repeating anotherHere some examples:<jupyter_code>S1[1:4]    # slicing an index
S2 + S3    # concatenating an index
S3*3       # Repetition<jupyter_output><empty_output><jupyter_text>Index operations will be discussed in more detail in the upcoming reading material.Another property of strings in Python is *immutability*. In the previous notebook you have learned about the concepts of mutability and immutability. Now, strings being immutable means they cannot be changed in place after they are created: any operations performed on strings cannot overwrite the values of a string object. But you can always build a new one and assign it to the same name. To illustrate that, let's look at two examples. Immutabilitity means, that you cannot change a single item of a string like this:<jupyter_code>S1[1]='y'<jupyter_output><empty_output><jupyter_text>Instead, we get a ```TypeError```, stating that string objects do not support item assignment! But we can run expressions to make new objects and reference them to the same name:<jupyter_code>S1 = 'y' + S1
print(S1)<jupyter_output><empty_output><jupyter_text>In this case, the old object and its reference are then deleted. In fact, Python cleans up old objects as you go. You will learn more about that in the upcoming reading material.  Formatted output of strings using ```print()```You have already used ```print()``` to quickly print variable to the screen. The function, however, can be fed with syntax that formats the output of strings and numbers. For that, two different flavors are possible. The original technique available since Python's beginning, which is based on the C language and is used widely:* String formatting expressions: ```'...%s...' % (values)```A newer technique added since Python 2.6:* String formatting method calls: ```'...{}...'.format(values)```The second method is syntactically a bit more complex, expecially since it uses object oriented syntax, which we will discuss at a later point in the course. However, it has a clear advantage, as type codes are optional and different object types handled automatically. Both flavors can be used without (as interactive echo of the interpreter) and with the ```print()``` function. Below you can find a list of type codes useful for the second option (string formatting expressions). The list is not complete, but contains all codes relevant for this course.Table 2: *Selected string Formatting Type Codes.*| Code           | Meaning / Object Type      | :-: | :- || ```%s```          | String   | ```%c```          | Character (int or str)   | ```%d```          | Decimal (base-10 integer)| ```%i```          | Integer| ```%e```          | Floating-point with exponent, lower case| ```%E```          | Same as ```e``` but uses upper case ```E```| ```%f```          | Floating-point decimal   | ```%```           | Literal % (coded as %%) In the following examples, both formatting techniques are adapted. Try to alter them and learn how they work:<jupyter_code>print("The %s robe is green!" % S2)          # formatting expression
print('The {} robe is green!'.format(S2))    # formatting method calls
knifes = 2
print("The %s has %i knifes in his hand." % (S2,knifes))
print("The {} has {} knifes in his hand.".format(S2,knifes))<jupyter_output><empty_output><jupyter_text>Precision of floating points can be controlled for the second formatting method by entering parameter into the curvy brackets, for example in the following way if you want to print two digits after the comma. Also the positions of the variable replacements can be switched:<jupyter_code>money = 2.222222
print("The {1:.3f} cents in the {0} pockets were stolen.".format(S2,money))
print("The {0:.3} cents in the {1:0.3} pockets were stolen.".format(S2,money))<jupyter_output><empty_output><jupyter_text>If you like to get into the details of the very flexible string formatting using method calls, check the following pages:* https://www.digitalocean.com/community/tutorials/how-to-use-string-formatters-in-python-3 * https://pyformat.info/  Type Specific Operations and MethodsLastly, I would like to provide an overview of type specific operations for strings in Python.Table 3: *String Type Specific Operations (after Lutz, 2013, Table 7-1).*| Operation                                     | Interpretation      | :-----------                                   | :----------- || ```S1 + S2```                                 | Concatenate   | ```S1 * 3```                                  | Repeat    | ```S[i]```                                    | Indexing   | ```S[i:j]```                                  | Slicing | ```len(S)```                                  | Length | ```"The sum of 1 + 2 is %i" % (1+2)```        | String formatting expression | ```"The sum of 1 + 2 is {0}".format(1+2)```   | String formatting method calls| ```.find('pa')```                            | String methods: search | ```.strip()```                               | Remove all leading and trailing whitespace| ```.rstrip()```                              | Remove trailing whitespace| ```.replace('pa','xx')```                    | Replacement| ```.split(',')```                            | Split on delimiter| ```.splitlines()```                          | split string at all ‘\n’ and return a list of the lines| ```.lower()```                               | Case conversion (to lower case)| ```.upper()```                               | Case conversion (to upper case)| ```.endswith(spam')```                       | End testThe first seven entries have been addressed in this notebook. All remaining entries are so called methods. Methods are specific functions that are applied with the following syntax: ```stringname.methodname(arguments)```. The methods in the table are specifically designed to handle strings. These methods may appear to alter the content of strings. However, they are actually not changing the original strings but create new strings as results - because strings are immutable.Investigate and practice the functionality of these methods. You can use the examples below, the Python ```help()``` function or search them in the Python documentation: https://docs.python.org/3/library/stdtypes.html (scroll down to  the section "String Methods"). Alternatively, study the following external Jupyter Notebook, which discusses the most important string methods: https://www.digitalocean.com/community/tutorials/an-introduction-to-string-functions-in-python-3<jupyter_code>S = 'Hello World ! '# define a string S
S.find('World')    # find the substring 'World'
S.replace('World','Class')        # replace the substring 'World' with 'Class'
S.rstrip(), S.lower(), S.upper()  # check what happened to the spaces and the letters
S.split(' ')       # splits the string at a given delimiter (here space)
S                  # even after the performed operations, the immutable string S remains unchanged
help(str.find)     # request help for a method<jupyter_output><empty_output><jupyter_text>Now, you can move on to read the book section about "Strings in Action" from Lutz (2013), which you can download on Canvas. The material will strengthen you knowledge about strings sequences, most importantly details about **indexing and slicing**. You can use the code cells below, to practice the examples in the book section.<jupyter_code># add your code here


# add your code here


# add your code here


# add your code here<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09429789416327447, 'fraction_numerical': 0.01185935446648931, 'mean_word_length': 4.695509309967141, 'pattern_counts': {'":': 0, '<': 192, '<?xml version=': 0, '>': 175, 'https://': 11, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '44', 'dir': 'jupyter-structured-clean-dedup', 'id': '187989', 'license': 'CC0-1.0', 'n_tokens_mistral': 8360, 'n_tokens_neox': 7914, 'n_words': 4031, 'path': 'Lessons/Lesson03_BasicObjectTypes.ipynb', 'repo_name': 'GeoPythonVT/geosf21', 'size': '31199'}