<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Pandas has two important data strucures Series and DataFrame
Series
Series is a one-dimensional labeled array capable of holding any data type (integers, strings, floating point numbers, Python objects, etc.)
```python
s = pd.Series(data, index=index)
```
<img src="images/fig_pd_series.png" alt="Pandas Series Example" height="250" width="250" align = "left">
Step1:  A Series is like a fixed-size dict in that you can get and set values by index label
Step2:  You can also use the index position to get and set the values
Step3:  Vectorized operations and label alignment with Series
Step4:  DataFrame
DataFrame is a 2-dimensional labeled data structure with columns of potentially different types. You can think of it like a spreadsheet or SQL table, or a dict of Series objects. It is generally the most commonly used pandas object. 
<img src="images/fig_pd_data_frame.png" alt="Pandas DataFrame Example" height="400" width="400" align = "left">
Step5:  Accessing Data
Step6:  Handling missing values
Step7:  GroupBy function
Splitting the data into groups based on some criteria
Applying a function to each group independently
Combining the results into a data structure
<img src="images/fig_pd_groupby.jpg" alt="Pandas GroupBy" height="350" width="350" align="left">
Step8:  Working with Text Data
Step9:  Working with Dates and TimeSeries Data
<ASSISTANT_TASK:>
Python Code:
person_height_ft = pd.Series([5.5,5.2,5.8,6.1,4.8],name='height',
                index = ['person_a','person_b','person_c','person_d','person_e'],dtype=np.float64)
person_height_ft
person_height_ft.values
person_height_ft.index
Explanation: Pandas has two important data strucures Series and DataFrame
Series
Series is a one-dimensional labeled array capable of holding any data type (integers, strings, floating point numbers, Python objects, etc.)
```python
s = pd.Series(data, index=index)
```
<img src="images/fig_pd_series.png" alt="Pandas Series Example" height="250" width="250" align = "left">
End of explanation
person_height_ft['person_c']
Explanation: A Series is like a fixed-size dict in that you can get and set values by index label
End of explanation
person_height_ft[3]
person_height_ft[0:3]
Explanation: You can also use the index position to get and set the values
End of explanation
person_height_mtr = (12* 2.54) * person_height_ft/100
person_height_mtr
# please note the index which is not same as height
person_weight_kg = pd.Series([70,55,73,68,66],name='weight',
                index = ['person_b','person_d','person_e','person_c','person_a'],dtype=np.float64)
person_weight_kg
#Let's calculate BMI
bmi = person_weight_kg/person_height_mtr**2
bmi
#BMI of person_c
68/(1.76784*1.76784)
Explanation: Vectorized operations and label alignment with Series
End of explanation
index = ['person_a','person_b','person_c','person_d','person_e']
df_person = pd.DataFrame({'height':[5.5,5.2,5.8,6.1,4.8],'weight':[66,70,68,55,73],
                   'gender':['male','male','female','male','female']}, index=index)
df_person.describe(include='all')
print(df_person.ndim)
print(df_person.shape)
print(df_person.dtypes)
print(df_person.columns)
print(df_person.index)
print(len(df_person))
df_person.info()
df_person.reset_index()
df_person.reset_index().set_index('gender')
another_index = ['a','b','c','d','e']
df1 = df_person.reset_index()
df1.index = another_index
df1
Explanation: DataFrame
DataFrame is a 2-dimensional labeled data structure with columns of potentially different types. You can think of it like a spreadsheet or SQL table, or a dict of Series objects. It is generally the most commonly used pandas object. 
<img src="images/fig_pd_data_frame.png" alt="Pandas DataFrame Example" height="400" width="400" align = "left">
End of explanation
# Accessing the column data
height = df_person['height']
height
type(height)
# Accessing multiple columns
df_person[['height','weight']]
# Accessinng one individual cell
df_person['height']['person_a']
# Accessing rows by index keys
df_person.loc['person_a']
# Accessing multiple rows using range
df_person['person_a':'person_c']
# Accessing using index position
print(df_person.iloc[0])
print("--------------")
print(df_person.iloc[0,2])
# Boolean indexinng
# all persons with height > 5.2 feet
df_person[df_person.height > 5.2]
# Boolean indexinng
# all persons with height > 5.2 feet and weight > 60kgs
df_person[(df_person.height > 5.2) & (df_person.weight > 60)]
df_person
# Let's add new column "age" to the DataFrame
df_person['age'] = pd.Series([30,28,26,19,42], index=index)
df_person
# Find all perons with age > 28
# Find females with age > 28
# Find max aged person
df_person[df_person.age==df_person.age.max()]
# Find max aged male person
# Find all persons having height > average height of the group
Explanation: Accessing Data
End of explanation
df_backup = df_person.copy()
df_person = df_backup.copy()
# Let's introduce few NaN values 
df_person.loc['person_a','age'] = np.NaN
df_person.iloc[2,2] = np.NaN
df_person.loc['person_e','height'] = np.NaN
df_person.loc['person_f'] = np.NaN
df_person['married'] = np.NaN
df_person
# how takes 'all' or 'any'
# dropping all of the rows if all of the values are np.NaN
df_person.dropna(how='all')
# how takes 'all' or 'any'
# dropping all of the columns if all of the values are np.NaN
df_person.dropna(axis=1,how='all')
# Filling all of the NaN values with zero
df_person.fillna(0)
# replace NaN weight with average weight of the group
#df_person.weight.mean()
df_person['weight'].fillna(df_person.weight.mean())
df_person.fillna(method='ffill')
df_person.fillna(method='bfill')
df_person[df_person['weight'].notnull()]
df_person[df_person.notnull()['age']]
df_person = df_person.dropna(how='all')
df_person = df_person.dropna(how='all',axis=1)
df_person
df_person = df_person.fillna(method='ffill')
df_person
df_person['bmi'] = df_person['weight']/(((12* 2.54) * df_person['height']/100)**2)
df_person
Explanation: Handling missing values
End of explanation
df_person
df_person_grp = df_person.groupby('gender')
print(type(df_person_grp))
for group,data in df_person_grp:
    print(group, data)
    print("--------------------------------")
df_person_grp.mean()
df_person_grp.mean().plot(kind='bar')
Explanation: GroupBy function
Splitting the data into groups based on some criteria
Applying a function to each group independently
Combining the results into a data structure
<img src="images/fig_pd_groupby.jpg" alt="Pandas GroupBy" height="350" width="350" align="left">
End of explanation
df_person.apply(lambda x: x['gender'].upper()[0], axis=1)
df_person.columns
df_person[['weight','height']].apply(lambda x: x.dtype)
df_person.mean()
df_person['gender'].str.upper().str[0]
Explanation: Working with Text Data
End of explanation
# settig seed ?
np.random.seed(5)
price = pd.Series(np.random.randint(100,high=150,size=150),
                  index=pd.date_range('2000-1-1', periods=150, freq='B'),name='col1')
price.head()
price.groupby(pd.TimeGrouper('1M')).max().plot(ylim=(146,150))
np.random.seed(5)
price1 = pd.Series(np.random.randint(200,high=250,size=500),
                   index=pd.date_range('2000-1-1', periods=500, freq='D'),name='col2')
#all_days = pd.date_range('2000-1-1', periods=500, freq='D')
df_time = pd.DataFrame({'col1':price,'col2':price1})
df_time.head()
len(df_time)
df_time.dtypes
df_time.plot(figsize=(16,8))
df_time.groupby(pd.TimeGrouper('1M')).mean().plot()
pd.Categorical?
pd.CategoricalIndex?
df
x = pd.Categorical(df_time['label'],ordered=True)
cat = pd.Series(df['label'], dtype=x)
cat
pd.merge?
pd.concat?
pd.Timestamp?
plt.plot([1,2,3,3.5,4.0],[1,2,3,3.2,3.8], 
         color='green', linestyle='dashed', 
         marker='o',markerfacecolor='blue', 
         markersize=8)
mylist = [0,1,0,1,2,3,4,0,1]
mycat = pd.Categorical(mylist,categories=[0,1])
mycat
mycat.set_categories([0,1,2,3,4])
Explanation: Working with Dates and TimeSeries Data
End of explanation
<END_TASK>