<jupyter_start><jupyter_text>**Data Science in Python workshop** <NAME>, Applied Science @ Discovery Channel___ ** teaching materials in these notebooks are solely the property of Bryce Macher, and cannot be used for other workshops or courses other than by the author or by license. Students are open to review these notebooks at any time, but with attribution of credit back to the author.  Python FoundationsThis notebook will just go through the basic topics in order:- Integers, Floats, Strings- Lists- Booleans, Comparison Operators- Control Flow (if, elif, else statements)- Iterators (for, try/except Loops)- User-defined functions  Our first programPython works by utilizing various base functions and methods. We can accentuate those using `import` packages, which we'll do extensively later on. Function: executes something that interacts with an object. Print is an example.<jupyter_code>#Here, we use the print function - note, anything that starts with a hashtag will be ignored by the computer
print('hello world')<jupyter_output>hello world<jupyter_text>Above, `print()` is our print function that does something to the object 'hello world'  Let's get into the Lingo!Python is an object-oriented language that is sometimes utilized functionally. Object-oriented - everyhing is an 'object' that has specific attributes. An object is kind of like a box that contains other boxes, and it inherits the attributes and methods of the box inside of itself.<jupyter_code>box = 'hello world'
print(box)
print(box.upper())<jupyter_output>HELLO WORLD<jupyter_text>Above, our object `box` inherits the methods that change case from the string object `hello world`. How do we know this is a string type? By intuition, we know because it's surrounded by quotes -- note, python doesn't care if you're a Brit or American, so use whichever quote mark makes the most sense at the time. But we can also check the type using the `type()` function.<jupyter_code>type(box)<jupyter_output><empty_output><jupyter_text>The above is an 'output', which is different from printing. An output is only visible to us as a notebook artifact and will be overwritten by the last thing outputting. If you actually want your code to return something, you should print it<jupyter_code>#Here, we have 3 outputs
type(123)
type(box)
type('123')
#Here, we return 3 types using the print function
print(type(123))
print(type(box))
print(type('1.2'))<jupyter_output><class 'int'>
<class 'str'>
<class 'str'><jupyter_text>**Knowledge Check:** Wait... why is that last item a string?!?! It's numbers?????  Data Types  Numerics<jupyter_code>#Integers are whole numbers, positive or negative. In this cell, check the types of 1, -1 using the print function
type(-1)
#Python can work like a pretty cool calculator
## Addition
print(1+1)

## Multiplication
print(1*1)

## Divide
print(1/1)

## Subtract
print(1-1-2)

## Exponents
print(2**2)

## Modulo 
print(3 % 3)
#Because objects inherit attributes of the thing inside of them, we can even do this with objects. 
#Python will even follow algebraic order of operations for you! Can you do the math below by hand - does it check out? 

num1 = 2
num2 = 3

print(num1+num2/(num2*num2) + num1)
# What datatype is 1.0? 
type(1.0)<jupyter_output><empty_output><jupyter_text>Floats will behave just like integers. Python will even help you interact with them and translate between integer and float for you. But you should know that they are different data types, because you will get burned by this when we get into machine learning!  CollectionsA collection is a group of data. Sometimes they have to be uniform; othertimes they do not. Sometimes you can manipulate them; sometimes you can't!<jupyter_code># A list is a collection of data in a linear fashion where order matters
type([1, 2, 3])
#It does not have to have a uniform data type
lst = ['hi', 1, 2, 3]
print(lst)
#lists can even carry other lists!

lst = ['hi', 1, 2, 3, ['sublist', 'is', 'good', 1]]
#objects in a list collection can be called by their position index, which starts at 0
print(lst[0])

#Run this cell - can you explain what just happened? 
#We can even go backwards! #can you explain why this returned a whole list??
print(lst[-1])
#how would you try to get the number 1 out of that list? Give it a try here. Learn to love error codes. They're completley normal
## You'll see me get tons!
print(lst[-1][-1])
print(lst[-1][3])

#Objects continue to have their same methods, so you can even call a string method against the first object in the list `lst`
print(lst[0].upper())
#But you can't do that if it's not a string!
print(str(lst[1]).upper())
#Lists also have their own methods. You can add things!
lst.append('d')
print(lst)
#append the word str "I did it!" to the sublist
lst.append('I did it')
new_list = [1, 2, 1, 2, 3]
set(new_list)
lst = []a
tup = ()
dictio = {}
sEt = {}
array = [[]]<jupyter_output><empty_output><jupyter_text>There are many other types of collections- dictionaries, sets, tuples, and generators are some of the most popular. This is an area for future exploration! For now, let's move on to comparing objects in python  ComparisonsComparitors evaluate the relationship between two objects, and return a boolean. Comparitor: You know these! They are ``, `=`, `!=`, etc. Because python uses `=` to assign objects, the comparitor to evaluate if something is equal is `==` - it's two equal signsBoolean: You also know this. It's basically a true/false exam for the computer.<jupyter_code># here is a comparitor returning a boolean!
print(1 > 2)
#You can compare anything these days
print('bat' == 'bat')
#And even compare objects that are the result of a function or method!
animal_1 = 'bat'
animal_2 = 'BAT'

print(animal_1 == animal_2.lower())
#Can you return the object type that is a result of the comparitor above? 
type(True)
#Because python is object oriented, you can stack functions and methods!
print(str(type(type(animal_1.upper() > animal_2))).upper())
#what did the cell above do? How would you break it apart? To better understand? 
##This is called 'debugging' or 'logical unit testing', and is a key method in evaluating whether your comparitor is working correctly

##Try debugging this and making notes for yourself!
word_1 = 'pat'
word_2 = 'tap'

word_1 == word_2[::-1]<jupyter_output><empty_output><jupyter_text>Logical OperatorsThese are ways of making comparisons more specific and complex. In python, the most typical are `and` and `or`. `and` is all-inclusive. This AND that have to be true for it to return true; if any part is false, the whole thing is false. `or` is partial-inclusive. This OR that have to be true for a comparison to return true; if any part is true, it is true.<jupyter_code>#What do you expect this to return? 
(1<2) and (2 < 3)<jupyter_output><empty_output><jupyter_text>this_is_snake_casethisIsCamelCase<jupyter_code>#And this? 
(word_1 == word_2[::-1]) or (len(word_1) < len(animal_2.lower()))
#Write a complicated piece of code. Go crazy! Play with your new super powers! Let's share in a few minutes.<jupyter_output><empty_output><jupyter_text>Intermediate PythonLet's start manipulating objects!  Control FlowControl flow is the way you control the flow of objects; or, how you set conditions that trigger actions. Here's an example - Put a comment at the end of each line to explain what you think it's doing!<jupyter_code>#control flow program 1!

sentence_1 = "Hello, I'm new here. My name is Comet!"
name = 'comet'

if name in sentence_1.lower():
  print('new dog: {}'.format(name))
else: 
  print('no dogs in this sentence.')
#We can even make our control flow interactive!
sentence_1 = "I'm comet!"
name = input('who are you looking for? ')

if name in sentence_1.lower():
  print('new dog: {}'.format(name))
else: 
  print('no dogs in this sentence.')
#Re-write the above program so that it works! There's a simple fix here. 

#check out my number game - explain how it works. Add comments throughout like I did

##Import the random number generator from random
from random import randint

##Store a number between 1 and 10
mystery_number = randint(1, 10)

guess = input('guess a number between 1 and 10: ') #input always returns a string. So we'll fix that in the code

if float(guess) == float(mystery_number):
  print('woh, you guessed it in the first try!')
else:
  second_guess = input("You didn't get it... try again? ")
  if float(second_guess) != float(mystery_number):
    print('sorry, better luck next time. The number was {}'.format(mystery_number))
  else:
    print('nice job - nailed it on your second try!')

## We can also set an 'else, if' for a second condition

temperature = 55

if temperature > 30 and temperature <50:
  print('not bad')
elif temperature > 50:
  print('it must be spring')
else:
  print('wintery, eh?')<jupyter_output>it must be spring<jupyter_text>IteratorsIterators are a way to scan over a collection in order to trigger some type of action.<jupyter_code>## We start with a list, for example: 
my_list = ['comet', 'bridget', 'sam']

for object in my_list:
  if object in ['comet', 'bridget']:
    print('{} the dog is in the list'.format(object))
  elif object in ['bryce', 'sam']:
    print('{} the human is in the list'.format(object))
  else:
    print('no one you know is at this party')
#We can also use iterators to manipulate objects!
## See if you can't commment out what this code is doing. 

if 'bryce' in my_list:
  for object in my_list:
    print(object.upper())
else:
  my_list.append('bryce')
  for object in my_list:
    print(object.upper())
#We can also try things, and if it doesn't work, we can bail. 
## Let's add a number to our list and get an error. 

my_list.append(3)

#now run the above:
if 'bryce' in my_list:
  for object in my_list:
    print(object.upper())
else:
  my_list.append('bryce')
  for object in my_list:
    print(object.upper())
#We can add a try and except to fix this. How would you explain this to a new programmer? Why is my name printing multiple times? 

if 'bryce' in my_list:
  for object in my_list:
    try:
      print(object.upper())
    except:
      pass
else:
  my_list.append('bryce')
  for object in my_list:
    try:
      print(object.upper())
    except:
      pass
x = 1

for num in range(1, 6):
  x = x*2
  print(x)<jupyter_output>2
4
8
16
32<jupyter_text>Custom FunctionsLet's say we were going to subject mutliple collections to the my_name test. We can minimize the amount of code writing by storing a process as a function!<jupyter_code>#We use the def keyword to flag we're about to do something. Here, it's very simple: 
def catExtractor(a_list_that_is_abstractly_defined): #this list is a placeholder for any list in the future
  if any(item in ['tiger', 'cat', 'ocelot', 'puma', 'cougar'] for item in a_list_that_is_abstractly_defined):
    print("There's a cat in this list")
  else:
    print('There are no cats in this list')
#Now, we can apply that function to multiple lists!
cat_list = ['ocelot', 'dolphin', 'bridget', 'comet', 'hippo']
catExtractor(cat_list)
#Let's try this. Why doesn't it work? 
cat_list = ['Ocelot', 'Puma', 'Tiger']
catExtractor(cat_list)
#Rewrite the function to return what 1) the names of the cats that are in the list, 2) regardless of capitalization!
def catExtractor(a_list):
  for word in a_list:
    for cat in ['tiger', 'cat', 'ocelot', 'puma', 'cougar']:
      if word.lower() == cat:
        print('there is a {} in the list'.format(word))
      else:
        pass
any(item in ['puma', 'tiger', 'banana'] for item in ['tiger', 'ocelot', 'puma'])
cat_list = ['Ocelot', 'Puma', 'Tiger']
catExtractor(cat_list)
a_lst = ['Ocelot', 'tiger', 'PUma']
list(map(str.lower, a_lst))<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.0987812153754368, 'fraction_numerical': 0.010653711753174805, 'mean_word_length': 4.649494463168031, 'pattern_counts': {'":': 0, '<': 52, '<?xml version=': 0, '>': 52, 'https://': 0, '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': '12', 'dir': 'jupyter-structured-clean-dedup', 'id': '423938', 'license': 'MIT', 'n_tokens_mistral': 3696, 'n_tokens_neox': 3541, 'n_words': 1643, 'path': '1_welcomeToPython.ipynb', 'repo_name': 'burgamacha/DS-Masterclass', 'size': '11739'}