{"cells":[{"metadata":{},"cell_type":"markdown","source":"<img src=\"https://coubsecure-s.akamaihd.net/get/b90/p/coub/simple/cw_timeline_pic/18b7e7583a5/ff29f23bd5ad52626a2d2/big_1411093836_1393413548_image.jpg\" width=\"700\">\n\n<br>\nThe data page suggests we take into account where birds live because birds in different areas may make different sounds. This notebook attempts to do the following things:\n\n - See where different species are found.\n - Compare calls for migrating species.\n - Compare calls for geo-diverse species.\n \nFor the samples I examine here, there is evidence that birds of the same species sound different depending on where they are. These features may prove useful:\n - Bird location\n - Seasonal range\n - Harmonic amplitude and percussive amplitude\n - Call intervals (top-to-top)\n \nBelow are two summary plots that appear later in the notebook. There are also audio clips and plots for individual calls.","execution_count":null},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"%%HTML\n\n<table><tr>\n<td> <img src=\"https://i.imgur.com/Fe96nJK.png\" width=500> </td>\n<td> <img src=\"https://i.imgur.com/3fPbUj6.png\" width=400> </td>\n</tr></table>","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-output":true,"_kg_hide-input":true},"cell_type":"code","source":"# ! conda update -n base -c defaults -y conda\n! conda install -c conda-forge -y ffmpeg # librosa","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":false},"cell_type":"code","source":"import re\nimport certifi\nimport urllib3\nfrom pathlib import Path\n\nfrom scipy import signal\nfrom dask import bag, diagnostics\nfrom IPython.display import Audio, HTML\nimport librosa\nimport numpy as np\nimport pandas as pd\nimport colorcet as cc\nimport geoviews as gv\nimport holoviews as hv\nfrom holoviews.operation.datashader import datashade, dynspread\nhv.extension('bokeh')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Overview\n\nThere are over 21,000 birds from 264 species represented in the metadata. Around 300 of the birds have no location specified.","execution_count":null},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"train = pd.read_csv('../input/birdsong-recognition/train.csv')\nprint(f\"{train.ebird_code.nunique()} species, {len(train)} birds.\")\nprint(\"subdirectories and files\")\n!ls -d ../input/birdsong-recognition/train_audio/* |wc","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"train = train.assign(latitude = pd.to_numeric(train.latitude, \n                                              errors='coerce'),\n                     longitude = pd.to_numeric(train.longitude, \n                                               errors='coerce'),\n                     date = pd.to_datetime(train.date, format=\"%Y-%m-%d\",\n                                           errors='coerce'),\n                     month = lambda x: x.date.dt.month) \\\n             .dropna(subset=['latitude', 'longitude', 'date'])\n    \nprint(len(train), train.species.nunique())","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"It's really easy to make interactive plots with the [Holoviz](https://holoviz.org/) library. One of the packages, Geoviews, is designed for geographic plots. It's scalable up to hundreds of millions of dots on a regular laptop. The only drawback is you need to have the notebook running live (as in edit mode) to get the full effects of zooming and datashading.\n\nBrighter spots indicate higher bird density.","execution_count":null},{"metadata":{"_kg_hide-input":false,"trusted":true},"cell_type":"code","source":"%%opts RGB {+axiswise} [width=650 height=620 xaxis=None yaxis=None] (alpha=0.3)\n\n# Just 4 lines\npoints_trn = gv.Points(train, kdims=['longitude', 'latitude'], vdims='species')\nspots_trn = datashade(points_trn, cmap=cc.kbc, normalization='eq_hist') \ntiles = gv.tile_sources.CartoLight\ntiles*dynspread(spots_trn, threshold=1.0)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"The birds can be grouped by genera, which are the first names of the species' scientific names. The top 21 genera by count give us 37% of the birds.","execution_count":null},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"genera = train.sci_name.str.extract('([a-zA-Z]+ )', expand=False).value_counts(normalize=True).cumsum()\ngenera.head(21)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Species and migration patterns\n\nHere is where these birds have been spotted. Not all birds migrate, but those who do tend to move to where the warm weather is. ***Blue dots indicate March through August, orange dots indicate September through February.***","execution_count":null},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"%%opts Points [width=250 height=220 xaxis=None yaxis=None] (alpha=0.3)\n\n\ndef plot_birds(train_part):\n    points_trn = gv.Points(train_part, kdims=['longitude', 'latitude'], \n                           vdims='species')\n    tiles = gv.tile_sources.CartoLight\n    chart = tiles*points_trn\n    return chart\n\n    \nlayout_list = []\nfor type in genera.index[:21]:\n    train_part = train[train.sci_name.str.contains(type)].copy()\n    species_count = train_part.species.nunique()\n    title = f\"{type} - ({species_count} species)\"\n    \n    mar_aug = train_part[train_part.month.between(3,8)]\n    sep_feb = train_part[(train_part.month < 3) | (train_part.month > 8)]\n    if not mar_aug.empty:\n        p1 = plot_birds(mar_aug)\n        if not sep_feb.empty:\n            p2 = plot_birds(sep_feb)\n            layout_list.append((p1*p2).relabel(title))\n        else: \n            layout_list.append(p1.relabel(title))\n    elif not sep_feb.empty:\n        p1 = p2 = plot_birds(sep_feb)  # force color consistency\n        layout_list.append((p1*p2).relabel(title))\n    \nlayout = hv.Layout(layout_list).cols(3)\ndisplay(layout)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Here are the 3 species making up the genus Contopas. All three appear to migrate.","execution_count":null},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"%%opts Points [width=250 height=220 xaxis=None yaxis=None] (alpha=0.3)\n\ncontopas = train.loc[train.sci_name.str.contains('Contopus'), 'species'].unique()\nlayout_list = []\nfor c in contopas:\n    train_part = train[train.species == c].copy()\n    mar_aug = train_part[train_part.month.between(3,8)]\n    sep_feb = train_part[(train_part.month < 3) | (train_part.month > 8)]\n    p1 = plot_birds(mar_aug)\n    p2 = plot_birds(sep_feb)\n    layout_list.append((p1*p2).relabel(train_part.iloc[0,9]))\n    \nhv.Layout(layout_list).cols(3)    ","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Comparison of calls during migration\n\n\nThe calls below are from one of the migrating Contopii, the Western Wood Pewee, aka Contopus sordidulus. We can look at summer calls vs. winter calls.\n\n<img src=\"https://upload.wikimedia.org/wikipedia/commons/9/96/Contopus_sordidulus_1.jpg\" width=\"400\">\n","execution_count":null},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"def show_sounds(bird, filename):\n    path = f\"../input/birdsong-recognition/train_audio/{bird}/{filename}\"\n    display(Audio(path))\n\n    y_, sr = librosa.load(path)\n    y_harm, y_perc = librosa.effects.hpss(y_[:100_000])\n    x_ = np.arange(100_000)/22050\n    xy_harm = np.stack((x_,y_harm), axis=1)\n    xy_perc = np.stack((x_,y_perc), axis=1)\n    \n    gram = hv.Curve(xy_harm).options(line_alpha=0.4) * hv.Curve(xy_perc).options(line_alpha=0.4)\n    display(gram.options(width=900, height=200))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"display(HTML('<h3 style=\"color:dodgerblue\">Summer (North America)</h3>'))\n\nsummer_files = train_part.loc[train_part.month.between(3,8), 'filename'].tolist()\nfor filename in summer_files[:3]:\n    show_sounds(\"wewpew\", filename)\n","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"display(HTML('<h3 style=\"color:red\">Winter (North America)</h3>'))\n\nwinter_files = train_part.loc[(train_part.month <= 3) | (train_part.month > 8), 'filename'].tolist()\nfor filename in winter_files[3:6]:\n    show_sounds(\"wewpew\", filename)\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"At first I didn't hear much difference between the two seasons compared to differences within seasons. But you see from the waveplots that the harmonic component of the call is more pronounced in the winter. Now when I listen to the first summer call and the first winter call I can hear the difference. It's like the bird is on vacation in South America and having fun!\n\nThe plot below shows a comparison of all the birds in the dataset.The winter birds generally have a higher harmonic component in their call. ","execution_count":null},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"def max_amp(filename, bird):\n    path = f\"../input/birdsong-recognition/train_audio/{bird}/{filename}\"\n    y, _ = librosa.load(path)\n    y_harm, y_perc = librosa.effects.hpss(y[:100_000])\n    return y_harm.max(), y_perc.max()\n   \n# Use dask for multiprocessing\ndef get_points(files, bird):\n    file_bag = bag.from_sequence(files).map(max_amp, bird)\n    with diagnostics.ProgressBar():\n        max_list = file_bag.compute()\n    return np.array(max_list)\n\nsummer_points = get_points(summer_files, \"wewpew\")\nwinter_points = get_points(winter_files, \"wewpew\")","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"metric = \"Relative Harmonicness of the Western Pewee\"\ns = hv.BoxWhisker(summer_points[:,0]/summer_points[:,1], label=\"Summer\").options(box_fill_color=\"orangered\")\nw = hv.BoxWhisker(winter_points[:,0]/winter_points[:,1], label=\"Winter\").options(box_fill_color=\"dodgerblue\")\n(s*w).options(width=500, height=300,title_format=metric)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Bird dialects\n\nHere I'll see if birds from different countries have different accents. The genus Corvus includes crows, ravens, and rooks - those classic black birds found across the world.\n\nThe Northern Raven, aka Corvus corax, is a good example of a bird who lives both in Europe and North America. Ravens are rather large birds and popular stars in science fiction and tales of horror.\n\n<img src=\"https://res-1.cloudinary.com/ebirdr/image/upload/s--uDfeyPbI--/f_auto,q_auto,t_full/3375-common-raven.jpg\" width=\"400\">\n\n\n","execution_count":null},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"%%opts Points [width=350 height=320 xaxis=None yaxis=None] (alpha=0.4)\n\ncorvus = \"Northern Raven\"\ntrain_part = train[train.species == corvus].copy()\nmar_aug = train_part[train_part.month.between(3,8)]\nsep_feb = train_part[(train_part.month < 3) | (train_part.month > 8)]\np1 = plot_birds(mar_aug)\np2 = plot_birds(sep_feb)\n(p1*p2).relabel(train_part.iloc[0,9])\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Let's compare sounds across the continents.","execution_count":null},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"display(HTML('<h3 style=\"color:dodgerblue\">American Raven</h3>'))\n\namerica_files = train_part.loc[train_part.longitude < -50, 'filename'].tolist()\nfor filename in america_files[1:4]:\n    show_sounds(\"comrav\", filename)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"display(HTML('<h3 style=\"color:dodgerblue\">European Raven</h3>'))\n\neurope_files = train_part.loc[train_part.longitude > -50, 'filename'].tolist()\nfor filename in europe_files[8:11]:\n    show_sounds(\"comrav\", filename)  \n    \n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"The harmonic and percussive components seem mostly consistent across continents, except for a couple of the european birds which are more sing-songy. One thing you might notice is that the American birds often have evenly spaced calls, whereas the European birds have more variation in the interval. \n\nThe plot below shows intervals for the samples above. Looking across all raven calls has challenges due to the varying nature of the recordings. The challenges can be overcome by using a more complex function.\n","execution_count":null},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"def peak_deviation(filename, bird):\n    path = f\"../input/birdsong-recognition/train_audio/{bird}/{filename}\"\n    y, _ = librosa.load(path)\n    _, y_perc = librosa.effects.hpss(y[:100_000])\n    peaks, _ = signal.find_peaks(y_perc, prominence=0.6*y_perc.max(), distance=3000)\n    if len(peaks) > 2:\n        intervals = np.diff(peaks[:5])\n        std = np.std(intervals[intervals>3000])\n    else:\n        std = 0\n    return std\n\n\n# Use dask for multiprocessing\ndef get_var(files, bird):\n    file_bag = bag.from_sequence(files).map(peak_deviation, bird)\n    with diagnostics.ProgressBar():\n        var_list = file_bag.compute(scheduler=\"threads\")\n    return np.array(var_list)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"americans = get_var(america_files[1:4], \"comrav\")\neuropeans = get_var(europe_files[8:12], \"comrav\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"%%opts BoxWhisker [width=400]\n\na = hv.BoxWhisker(americans[americans!=0]/22050, label=\"American Raven\").options(box_fill_color=\"red\")\ne = hv.BoxWhisker(europeans[europeans!=0]/22050, label=\"European Raven\").options(box_fill_color=\"royalblue\")\n(a*e).relabel(\"StDev of Cawing Interval\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"\n## Closing\nOverall this seems to be a very challenging task. These features may prove useful:\n - Bird location\n - Seasonal range\n - Harmonic amplitude and percussive amplitude\n - Call intervals between peaks\n","execution_count":null}],"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat":4,"nbformat_minor":4}