#%load_ext rpy2.ipython
#%R install.packages("nycflights13", repos='http://cran.us.r-project.org')
#%R library(nycflights13)
#%R write.csv(flights, "flights.csv")

"""
Explanation: Tom Augspurger Dplyr/Pandas comparison (copy of 2016-01-01)
See result there
http://nbviewer.ipython.org/urls/gist.githubusercontent.com/TomAugspurger/6e052140eaa5fdb6e8c0/raw/627b77addb4bcfc39ab6be6d85cb461e956fb3a3/dplyr_pandas.ipynb
to reproduce on your WinPython you'll need to get flights.csv  in this directory
This notebook compares pandas
and dplyr.
The comparison is just on syntax (verbage), not performance. Whether you're an R user looking to switch to pandas (or the other way around), I hope this guide will help ease the transition.
We'll work through the introductory dplyr vignette to analyze some flight data.
I'm working on a better layout to show the two packages side by side.
But for now I'm just putting the dplyr code in a comment above each python call.
using R steps to get flights.csv
un-comment the next cell unless you have installed R and want to get Flights example from the source
to install R on your Winpython:
how to install R
End of explanation
"""


# Downloading and unzipg a file, without R method :
# source= http://stackoverflow.com/a/34863053/3140336
import io
from zipfile import ZipFile
import requests

def get_zip(file_url):
    url = requests.get(file_url)
    zipfile = ZipFile(io.BytesIO(url.content))
    zip_names = zipfile.namelist()
    if len(zip_names) == 1:
        file_name = zip_names.pop()
        extracted_file = zipfile.open(file_name)
        return extracted_file

url=r'https://github.com/winpython/winpython_afterdoc/raw/master/examples/nycflights13_datas/flights.zip'
with io.open("flights.csv", 'wb') as f:
        f.write(get_zip(url).read())


# Some prep work to get the data from R and into pandas
%matplotlib inline
import matplotlib.pyplot as plt
#%load_ext rpy2.ipython

import pandas as pd
import seaborn as sns

pd.set_option("display.max_rows", 5)

"""
Explanation: using an internet download to get  flight.qcsv
End of explanation
"""


flights = pd.read_csv("flights.csv", index_col=0)

# dim(flights)   <--- The R code
flights.shape  # <--- The python code

# head(flights)
flights.head()

"""
Explanation: Data: nycflights13
End of explanation
"""


# filter(flights, month == 1, day == 1)
flights.query("month == 1 & day == 1")

"""
Explanation: Single table verbs
dplyr has a small set of nicely defined verbs. I've listed their closest pandas verbs.
<table>
  <tr>
    <td><b>dplyr</b></td>
    <td><b>pandas</b></td>
  </tr>
  <tr>
    <td><code>filter()</code> (and <code>slice()</code>)</td>
    <td><code>query()</code> (and <code>loc[]</code>, <code>iloc[]</code>)</td>
  </tr>
  <tr>
    <td><code>arrange()</code></td>
    <td><code>sort_values</code> and <code>sort_index()</code></td>
  </tr>
  <tr>
  <td><code>select() </code>(and <code>rename()</code>)</td>
    <td><code>__getitem__ </code> (and <code>rename()</code>)</td>
  </tr>
  <tr>
  <td><code>distinct()</code></td>
    <td><code>drop_duplicates()</code></td>
  </tr>
  <tr>
    <td><code>mutate()</code> (and <code>transmute()</code>)</td>
    <td>assign</td>
  </tr>
  <tr>
    <td>summarise()</td>
    <td>None</td>
  </tr>
  <tr>
    <td>sample_n() and sample_frac()</td>
    <td><code>sample</code></td>
  </tr>
  <tr>
    <td><code>%>%</code></td>
    <td><code>pipe</code></td>
  </tr>

</table>

Some of the "missing" verbs in pandas are because there are other, different ways of achieving the same goal. For example summarise is spread across mean, std, etc. It's closest analog is actually the .agg method on a GroupBy object, as it reduces a DataFrame to a single row (per group). This isn't quite what .describe does.
I've also included the pipe operator from R (%&gt;%), the pipe method from pandas, even though it isn't quite a verb.
Filter rows with filter(), query()
End of explanation
"""


# flights[flights$month == 1 & flights$day == 1, ]
flights[(flights.month == 1) & (flights.day == 1)]

# slice(flights, 1:10)
flights.iloc[:9]

"""
Explanation: We see the first big language difference between R and python.
Many python programmers will shun the R code as too magical.
How is the programmer supposed to know that month and day are supposed to represent columns in the DataFrame?
On the other hand, to emulate this very convenient feature of R, python has to write the expression as a string, and evaluate the string in the context of the DataFrame.
The more verbose version:
End of explanation
"""


# arrange(flights, year, month, day) 
flights.sort_values(['year', 'month', 'day'])

# arrange(flights, desc(arr_delay))
flights.sort_values('arr_delay', ascending=False)

"""
Explanation: Arrange rows with arrange(), sort()
End of explanation
"""


# select(flights, year, month, day) 
flights[['year', 'month', 'day']]

# select(flights, year:day) 
flights.loc[:, 'year':'day']

# select(flights, -(year:day)) 

# No direct equivalent here. I would typically use
# flights.drop(cols_to_drop, axis=1)
# or fligths[flights.columns.difference(pd.Index(cols_to_drop))]
# point to dplyr!

# select(flights, tail_num = tailnum)
flights.rename(columns={'tailnum': 'tail_num'})['tail_num']

"""
Explanation: It's worth mentioning the other common sorting method for pandas DataFrames, sort_index. Pandas puts much more emphasis on indicies, (or row labels) than R.
This is a design decision that has positives and negatives, which we won't go into here. Suffice to say that when you need to sort a DataFrame by the index, use DataFrame.sort_index.
Select columns with select(), []
End of explanation
"""


# rename(flights, tail_num = tailnum)
flights.rename(columns={'tailnum': 'tail_num'})

"""
Explanation: But like Hadley mentions, not that useful since it only returns the one column. dplyr and pandas compare well here.
End of explanation
"""


# distinct(select(flights, tailnum))
flights.tailnum.unique()

"""
Explanation: Pandas is more verbose, but the the argument to columns can be any mapping. So it's often used with a function to perform a common task, say df.rename(columns=lambda x: x.replace('-', '_')) to replace any dashes with underscores. Also, rename (the pandas version) can be applied to the Index.
One more note on the differences here.
Pandas could easily include a .select method.
xray, a library that builds on top of NumPy and pandas to offer labeled N-dimensional arrays (along with many other things) does just that.
Pandas chooses the .loc and .iloc accessors because any valid selection is also a valid assignment. This makes it easier to modify the data.
python
flights.loc[:, 'year':'day'] = data
where data is an object that is, or can be broadcast to, the correct shape.
Extract distinct (unique) rows
End of explanation
"""


# distinct(select(flights, origin, dest))
flights[['origin', 'dest']].drop_duplicates()

"""
Explanation: FYI this returns a numpy array instead of a Series.
End of explanation
"""


# mutate(flights,
#   gain = arr_delay - dep_delay,
#   speed = distance / air_time * 60)

flights.assign(gain=flights.arr_delay - flights.dep_delay,
               speed=flights.distance / flights.air_time * 60)

# mutate(flights,
#   gain = arr_delay - dep_delay,
#   gain_per_hour = gain / (air_time / 60)
# )

(flights.assign(gain=flights.arr_delay - flights.dep_delay)
        .assign(gain_per_hour = lambda df: df.gain / (df.air_time / 60)))


"""
Explanation: OK, so dplyr wins there from a consistency point of view. unique is only defined on Series, not DataFrames.
Add new columns with mutate()
We at pandas shamelessly stole this for v0.16.0.
End of explanation
"""


# transmute(flights,
#   gain = arr_delay - dep_delay,
#   gain_per_hour = gain / (air_time / 60)
# )
(flights.assign(gain=flights.arr_delay - flights.dep_delay)
        .assign(gain_per_hour = lambda df: df.gain / (df.air_time / 60))
        [['gain', 'gain_per_hour']])


"""
Explanation: The first example is pretty much identical (aside from the names, mutate vs. assign).
The second example just comes down to language differences. In R, it's possible to implement a function like mutate where you can refer to gain in the line calcuating gain_per_hour, even though gain hasn't actually been calcuated yet.
In Python, you can have arbitrary keyword arguments to functions (which we needed for .assign), but the order of the argumnets is arbitrary since dicts are unsorted and **kwargs* is a dict. So you can't have something like df.assign(x=df.a / df.b, y=x **2), because you don't know whether x or y will come first (you'd also get an error saying x is undefined.
To work around that with pandas, you'll need to split up the assigns, and pass in a callable to the second assign. The callable looks at itself to find a column named gain. Since the line above returns a DataFrame with the gain column added, the pipeline goes through just fine.
End of explanation
"""


# summarise(flights,
#   delay = mean(dep_delay, na.rm = TRUE))
flights.dep_delay.mean()

"""
Explanation: Summarise values with summarise()
End of explanation
"""


# sample_n(flights, 10)
flights.sample(n=10)

# sample_frac(flights, 0.01)
flights.sample(frac=.01)

"""
Explanation: This is only roughly equivalent.
summarise takes a callable (e.g. mean, sum) and evaluates that on the DataFrame. In pandas these are spread across pd.DataFrame.mean, pd.DataFrame.sum. This will come up again when we look at groupby.
Randomly sample rows with sample_n() and sample_frac()
End of explanation
"""


# planes <- group_by(flights, tailnum)
# delay <- summarise(planes,
#   count = n(),
#   dist = mean(distance, na.rm = TRUE),
#   delay = mean(arr_delay, na.rm = TRUE))
# delay <- filter(delay, count > 20, dist < 2000)

planes = flights.groupby("tailnum")
delay = (planes.agg({"year": "count",
                     "distance": "mean",
                     "arr_delay": "mean"})
               .rename(columns={"distance": "dist",
                                "arr_delay": "delay",
                                "year": "count"})
               .query("count > 20 & dist < 2000"))
delay

"""
Explanation: Grouped operations
End of explanation
"""


fig, ax = plt.subplots(figsize=(12, 6))

sns.regplot("dist", "delay", data=delay, lowess=True, ax=ax,
            scatter_kws={'color': 'k', 'alpha': .5, 's': delay['count'] / 10}, ci=90,
            line_kws={'linewidth': 3});

"""
Explanation: For me, dplyr's n() looked is a bit starge at first, but it's already growing on me.
I think pandas is more difficult for this particular example.
There isn't as natural a way to mix column-agnostic aggregations (like count) with column-specific aggregations like the other two. You end up writing could like .agg{'year': 'count'} which reads, "I want the count of year", even though you don't care about year specifically. You could just as easily have said .agg('distance': 'count').
Additionally assigning names can't be done as cleanly in pandas; you have to just follow it up with a rename like before.
We may as well reproduce the graph. It looks like ggplots geom_smooth is some kind of lowess smoother. We can either us seaborn:
End of explanation
"""


import statsmodels.api as sm

smooth = sm.nonparametric.lowess(delay.delay, delay.dist, frac=1/8)
ax = delay.plot(kind='scatter', x='dist', y = 'delay', figsize=(12, 6),
                color='k', alpha=.5, s=delay['count'] / 10)
ax.plot(smooth[:, 0], smooth[:, 1], linewidth=3);
std = smooth[:, 1].std()
ax.fill_between(smooth[:, 0], smooth[:, 1] - std, smooth[:, 1] + std, alpha=.25);

# destinations <- group_by(flights, dest)
# summarise(destinations,
#   planes = n_distinct(tailnum),
#   flights = n()
# )

destinations = flights.groupby('dest')
destinations.agg({
    'tailnum': lambda x: len(x.unique()),
    'year': 'count'
    }).rename(columns={'tailnum': 'planes',
                       'year': 'flights'})

"""
Explanation: Or using statsmodels directly for more control over the lowess, with an extremely lazy
"confidence interval".
End of explanation
"""


destinations = flights.groupby('dest')
r = destinations.agg({'tailnum': {'planes': lambda x: len(x.unique())},
                      'year': {'flights': 'count'}})
r

"""
Explanation: There's a little know feature to groupby.agg: it accepts a dict of dicts mapping
columns to {name: aggfunc} pairs. Here's the result:
End of explanation
"""


# daily <- group_by(flights, year, month, day)
# (per_day   <- summarise(daily, flights = n()))

daily = flights.groupby(['year', 'month', 'day'])
per_day = daily['distance'].count()
per_day

# (per_month <- summarise(per_day, flights = sum(flights)))
per_month = per_day.groupby(level=['year', 'month']).sum()
per_month

# (per_year  <- summarise(per_month, flights = sum(flights)))
per_year = per_month.sum()
per_year

"""
Explanation: The result is a MultiIndex in the columns which can be a bit awkard to work with (you can drop a level with r.columns.droplevel()). Also the syntax going into the .agg may not be the clearest.
Similar to how dplyr provides optimized C++ versions of most of the summarise functions, pandas uses cython optimized versions for most of the agg methods.
End of explanation
"""


# flights %>%
#   group_by(year, month, day) %>%
#   select(arr_delay, dep_delay) %>%
#   summarise(
#     arr = mean(arr_delay, na.rm = TRUE),
#     dep = mean(dep_delay, na.rm = TRUE)
#   ) %>%
#   filter(arr > 30 | dep > 30)
(
flights.groupby(['year', 'month', 'day'])
    [['arr_delay', 'dep_delay']]
    .mean()
    .query('arr_delay > 30 | dep_delay > 30')
)

"""
Explanation: I'm not sure how dplyr is handling the other columns, like year, in the last example. With pandas, it's clear that we're grouping by them since they're included in the groupby. For the last example, we didn't group by anything, so they aren't included in the result.
Chaining
Any follower of Hadley's twitter account will know how much R users love the %&gt;% (pipe) operator. And for good reason!
End of explanation
"""


