import pandas as pd

# There are two data structures in pandas, Series and DataFrames
city_names = pd.Series(['San Francisco', 'San Jose', 'Sacramento'])
population = pd.Series([852469, 1015785, 485199])

pd.DataFrame({"City Name": city_names, "Population": population})

# importing an existing csv file into DataFrame
california_housing_dataframe = pd.read_csv(
    "https://storage.googleapis.com/mledu-datasets/california_housing_train.csv",
    sep=","
)

california_housing_dataframe.shape

california_housing_dataframe.head()

california_housing_dataframe.hist('housing_median_age')

"""
Explanation: Following the tutorial at: https://colab.research.google.com/notebooks/mlcc/intro_to_pandas.ipynb?hl=tr#scrollTo=U5ouUp1cU6pC
End of explanation
"""


cities = pd.DataFrame({'City Name': city_names, 'Population': population})
print(type(cities['City Name']))
cities['City Name']

print(type(cities["City Name"][1]))
cities["City Name"][1]

print(type(cities[0:2]))
cities[0:2]

"""
Explanation: Accessing Data
You can access DataFrame data using familiar Python dict/list operations:
End of explanation
"""


population / 1000

import numpy as np
np.log(population)

cities['Area square miles'] = pd.Series([46.87, 176.53, 97.92])
cities['Population density'] = cities['Population'] / cities['Area square miles']
cities

population.apply(lambda val: val > 1000000)

"""
Explanation: Manipulating Data
You may apply Python's basic arithmetic operations to Series. For example:
End of explanation
"""


cities['is saint and wide'] = (cities['Area square miles'] > 50) & (cities['City Name'].apply(lambda name: name.startswith("San")))
cities

"""
Explanation: Exercise #1
Modify the cities table by adding a new boolean column that is True if and only if both of the following are True:

The city is named after a saint.
The city has an area greater than 50 square miles.

Note: Boolean Series are combined using the bitwise, rather than the traditional boolean, operators. For example, when performing logical and, use &amp; instead of and.
Hint: "San" in Spanish means "saint."
End of explanation
"""


city_names.index

cities.index

cities.reindex([2, 0, 1])

"""
Explanation: Indexes
Both Series and DataFrame objects also define an index property that assigns an identifier value to each Series item or DataFrame row. 
By default, at construction, pandas assigns index values that reflect the ordering of the source data. Once created, the index values are stable; that is, they do not change when data is reordered.
End of explanation
"""


cities.reindex(np.random.permutation(cities.index))

"""
Explanation: Reindexing is a great way to shuffle (randomize) a DataFrame. In the example below, we take the index, which is array-like, and pass it to NumPy's random.permutation function, which shuffles its values in place. Calling reindex with this shuffled array causes the DataFrame rows to be shuffled in the same way.
End of explanation
"""


cities.reindex([4, 2, 1, 3, 0])

"""
Explanation: Exercise #2
The reindex method allows index values that are not in the original DataFrame's index values. Try it and see what happens if you use such values! Why do you think this is allowed?
End of explanation
"""


