<jupyter_start><jupyter_text>Functions: Intermediate  Default Arguments - Write a function named open_dataset() that takes the name of a CSV file in as the input and returns the file as a list of lists.    - Initiate the function with the default argument 'AppleStore.csv'. Name the parameter as you wish.    - Inside the function definition:    - Open the file using the open() function.    - Read in the opened file using the reader() function, which we import from the csv module.    - Use list() to convert the output returned by reader() to a list of lists.    - Use the open_dataset() function to open the AppleStore.csv file.    - Use the open_dataset() function by taking advantage of the default argument.    - Assign the data set to a variable named apps_data.    - Inspect the apps_data data set after you run the code to confirm that everything went as expected.     - You can try to print the first few rows or just use the variable inspector of the code editor.<jupyter_code>def open_dataset(file_name='AppleStore.csv'):
    opened_file = open(file_name, encoding="utf8")
    from csv import reader
    read_file = reader(opened_file)
    data = list(read_file)
    
    return data

apps_data = open_dataset()
apps_data = [row[1:] for row in apps_data]
print(apps_data[0])<jupyter_output>['id', 'track_name', 'size_bytes', 'currency', 'price', 'rating_count_tot', 'rating_count_ver', 'user_rating', 'user_rating_ver', 'ver', 'cont_rating', 'prime_genre', 'sup_devices.num', 'ipadSc_urls.num', 'lang.num', 'vpp_lic']<jupyter_text>The Official Python Documentation Use this [link](https://docs.python.org/3/) to access the official python documentation<jupyter_code>open?
import sys
sys.executable
from platform import python_version
print(python_version())
round?
one_decimal = round(3.43, 1)
print(one_decimal)
two_decimals = round(0.23321, 2)
print(two_decimals)
five_decimals = round(921.2225227, 5)
print(five_decimals)<jupyter_output>3.4
0.23
921.22252<jupyter_text>Using Multiple Return Statements<jupyter_code>apps_data[0]
def open_dataset(file_name='AppleStore.csv', header=True):
    opened_file = open(file_name, encoding="utf8")
    from csv import reader
    read_file = reader(opened_file)
    data = list(read_file)
    
    if header == True:
        return data[1:]
    else:
        return data
    
apps_data = open_dataset()
apps_data = [row[1:] for row in apps_data]
print(apps_data[0])
def sum_and_difference(a, b):
    a_sum = a + b
    difference = a - b
    return a_sum, difference

sum_diff =sum_and_difference(10, 5)
print(sum_diff)
type(sum_diff)<jupyter_output>(15, 5)<jupyter_text>- One thing you might have found a bit odd is the structure of the output `(20, 10)`. `(20, 10)` is a tuple, which is a data type that is very similar to a list (recall that examples of data types include integers, strings, lists, dictionaries, etc.).- Just as a list, a tuple is usually used for storing multiple values. Creating a tuple is similar to creating a list, with the exception that we need to use parentheses instead of brackets.- Just as lists, tuples support positive and negative indexing.- The main difference between tuples and lists boils down to whether we can modify the existing values or not. In the case of tuples, we can't modify the existing values, while in the case of lists, we can. Below, we're trying to modify the first value of a list and a tuple.- Tuples are called **immutable** data types because we can't change their state after they've been created. Conversely, lists are mutable data types because their state can be changed after they've been created. The only way we could modify tuples, and immutable data types in general, is by recreating them. This is a list of all the **mutable** and immutable data types we've learned so far. | Mutable| Immutable| --- | --- || Tuples | Lists || Integers | Dictionary || Floats || Strings || Booleans |<jupyter_code>def open_dataset(file_name='AppleStore.csv', header=True):
    opened_file = open(file_name, encoding="utf8")
    from csv import reader
    read_file = reader(opened_file)
    data = list(read_file)
    
    if header:
        return data[1:], data[0]
    else:
        return data

all_data = open_dataset()
header = all_data[1]
header = header[1:]
print(header)
apps_data = all_data[0]
# print(apps_data)<jupyter_output>['id', 'track_name', 'size_bytes', 'currency', 'price', 'rating_count_tot', 'rating_count_ver', 'user_rating', 'user_rating_ver', 'ver', 'cont_rating', 'prime_genre', 'sup_devices.num', 'ipadSc_urls.num', 'lang.num', 'vpp_lic']<jupyter_text>More About Tuples When we create a **`tuple`**, surrounding the values with parentheses is optional. It's enough to write the individual values and separate each with a comma. Below, we see two ways of creating a tuple (on the right, we're not using parentheses):<jupyter_code>a_tuple = (1, 'a')
print(a_tuple)
print(type(a_tuple))

a_tuple = 1, 'a'
print(a_tuple)
print(type(a_tuple))<jupyter_output>(1, 'a')
<class 'tuple'>
(1, 'a')
<class 'tuple'><jupyter_text>With this in mind, remember the syntax we used in the **`return`** statement to return multiple values:<jupyter_code>def sum_and_difference(a, b):
    a_sum = a + b
    difference = a - b
    return a_sum, difference

sum_diff = sum_and_difference(15, 5)
print(sum_diff)
print(type(sum_diff))<jupyter_output>(20, 10)
<class 'tuple'><jupyter_text>When we use return a_sum, difference, Python thinks we want the tuple a_sum, difference returned. This is why multiple variables are returned as **`tuples`**. If we wanted to return a **`list`** instead of a tuple, we need to use brackets: $[value]$<jupyter_code>def sum_and_difference(a, b):
    a_sum = a + b
    difference = a - b
    return [a_sum, difference]

sum_diff = sum_and_difference(15, 5)
print(sum_diff)
print(type(sum_diff))<jupyter_output>[20, 10]
<class 'list'><jupyter_text>When we work with tuples, we can assign their individual elements to separate variables in a single line of code.<jupyter_code>a_tuple = 1, 2
first_element = a_tuple[0]
second_element = a_tuple[1]

print(first_element)
print(second_element)

# Alternatively we can:

a_tuple = 1, 2
first_element, second_element = a_tuple

print(first_element)
print(second_element)<jupyter_output>1
2
1
2<jupyter_text>We can do the same with lists — we can assign individual list elements to separate variables in a single line of code:<jupyter_code>a_list = [3, 4]
first_element = a_list[0]
second_element = a_list[1]

print(first_element)
print(second_element)

# Alternatively we can:

a_list = [3, 4]
first_element, second_element = a_list

print(first_element)
print(second_element)<jupyter_output>3
4
3
4<jupyter_text>We can use this variable assignment technique with functions that **`return`** multiple variables.<jupyter_code>def sum_and_difference(a, b):
    a_sum = a + b
    difference = a - b
    return a_sum, difference

a_sum, a_diff = sum_and_difference(15, 5)

print(a_sum)
print(a_diff)<jupyter_output>20
10<jupyter_text>- Use the open_dataset() function to open the `AppleStore.csv` file, which has a header row.    - Do the variable assignment step in a single line of code.        - Assign the header to a variable named header.        - Assign the rest of the data set to a variable named apps_data.<jupyter_code>def open_dataset(file_name='AppleStore.csv', header=True):        
    opened_file = open(file_name, encoding="utf8")
    from csv import reader
    read_file = reader(opened_file)
    data = list(read_file)
    
    if header:
        return data[1:], data[0]
    else:
        return data

apps_data, header = open_dataset()

print(type(apps_data))
print(type(header))<jupyter_output><class 'list'>
<class 'list'><jupyter_text>Code Running Quirks So far, we've been using parameters and `return` statements for all of our functions. Note, however, that parameters and `return` statements are optional:<jupyter_code>def print_constant():
    x = 3.14
    print(x)

print_constant()<jupyter_output>3.14<jupyter_text>Functions without a `return` statement don't return any value. However, strictly speaking, they `return` a **`None`** value, which practically represents the absence of a value. The None value is an instance of the **`NoneType`** data type (just like 5.321 is an instance of the float data type).<jupyter_code>def print_constant():
    x = 3.14
    print(x)

j = print_constant()
print(j)
print(type(j))<jupyter_output>3.14
None
<class 'NoneType'><jupyter_text>In the function above, notice also that we assigned **`3.14`** to a variable named **`x`**. Although we clearly defined **`x`**, it turns out that we can't access **`x`** outside the function definition — Python raises a **`NameError`** and says that **`x`** is not defined.<jupyter_code>def print_constant():
    x = 3.14
    print(x)
    
x<jupyter_output><empty_output><jupyter_text>To debug the code above, let's start by mentioning that **_Python doesn't run the code we write inside a function's definition until we call that function_**. In the code example above, x = 3.14 is never run.This behavior applies to every function we create. For instance, below we're trying to perform a division between an empty list and a string, but no error is returned, which is proof that the code inside the function's definition is not executed:<jupyter_code>def divide():
    [] / 'abc'
    
print('Code finished running, but no error was returned')<jupyter_output>Code finished running, but no error was returned<jupyter_text>As we've already mentioned, Python executes the code inside a function's definition only when the function is called. Above, we'd get an error only if we called divide() because only then Python would run [] / 'abc'.<jupyter_code>divide()<jupyter_output><empty_output><jupyter_text>If Python runs the code inside a function definition only when the function is called, it seems that in order to debug the code below we first need to run **`print_constant()`** to get **`x = 3.14`** executed. - Rewrite the print_constant() function above.- Call the print_constant() function to make sure x = 3.14 gets executed.- Print the variable x using the print() function.    - What do you notice about the output?    - This may be totally unexpected, and we'll explain why this happens in the next screen.<jupyter_code>def print_constant():
    x = 3.14
    print (x)
    
print_constant()
print(x)<jupyter_output>3.14<jupyter_text>Scopes - Global and Local You might have found the error we got in the previous exercise completely unexpected. After all, we called the print_constant() function, which means that x = 3.14 must have been executed. So why did we still get an error telling us that x is undefined? When print_constant() is called, x = 3.14 is indeed executed, but the quirk is that Python only saves the x variable temporarily. Python saves x into a kind of temporary memory, which is immediately erased after the print_constant() finishes running.This explains why x is still undefined even after print_constant() is called — the temporary memory associated with print_constant() is immediately erased after the function finishes running, being freed up for later use.This kind of temporary memory storage doesn't also apply to the code that is being run outside function definitions. If we define x = 3.14 in our main program (outside function definitions), we can use x later on without having to worry that it was erased from memory.<jupyter_code>x = 3.14

print('random code')
print('more random code')<jupyter_output>random code
more random code<jupyter_text>The temporary memory associated with a function is isolated from the memory associated with the main program. The consequence of this is that we can initialize a variable `x = 10` in the main program, and then execute `x = 3.14` in the body of a function without overwriting the x variable of the main program.<jupyter_code>e = 'mathematical constant'
a_sum = 1000
length = 50

def exponential(x):
    e = 2.72
    print (e)
    return e**x

result = exponential(5)
print(result)

def divide():
    print(a_sum)
    print(length)
    return a_sum / length

result_2 = divide()
print(result_2)<jupyter_output>2.72
148.88279736320004
1000
50
20.0
{'fraction_non_alphanumeric': 0.09212141045569827, 'fraction_numerical': 0.016935285936349506, 'mean_word_length': 4.763319189061764, 'pattern_counts': {'":': 0, '<': 67, '<?xml version=': 0, '>': 67, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, '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': '19', 'dir': 'jupyter-structured-clean-dedup', 'id': '663649', 'license': 'MIT', 'n_tokens_mistral': 3867, 'n_tokens_neox': 3664, 'n_words': 1487, 'path': 'dataquest/01-python_for_data_science_fundamentals/dataquest-projects-master/Functions-Intermediate.ipynb', 'repo_name': 'pbittencourt/python4DS', 'size': '12223'}