diff --git a/README.md b/README.md index dd4681d3..86f2f3e1 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Our team processed and analyzed these databases to provide valuable information - selecting_reference_data (Creates visualizations and a dataset of representative citizen observations selected as baselines) - validation_labels (Flags citizen observations dropped during the data cleaning process and gives reasons for dropping them) - visualizations (Creates visualizations of the citizen and reference data) + - year_to_year_transition_times_data_generation (Creates the year_to_year_transition_time dataset) ### data (Contains CSV files of original data and data produced by the Python notebooks in code) - citizen_states_cleaned (Cleaned and reformatted citizen database sorted by states) - india_map (Geographic data used for finding the Inidan state given a set of coordinates) @@ -40,6 +41,7 @@ Our team processed and analyzed these databases to provide valuable information - selected_reference_data (Dataset of representative citizen observations selected as baselines) - species codes (Dataset mapping tree species ids to names) - validation_labels_alldata (Citizen database given by SeasonWatch with citizen observations dropped during the data cleaning process flagged and reasons for dropping them given) + - year_to_year_transition_time (Dataset of max and mean transition time and probability of phenophases) ### dev_code (Contains Python notebooks used in the development process) - jobfiles (Files of jobs submitted to shared cloud computing service) - scc-config (Config for submitting jobs to shared cloud computing service) diff --git a/code/year_to_year_transition_times_data_generation.ipynb b/code/year_to_year_transition_times_data_generation.ipynb new file mode 100644 index 00000000..3410fc2c --- /dev/null +++ b/code/year_to_year_transition_times_data_generation.ipynb @@ -0,0 +1,1432 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "a1f12ee0-1d01-404a-9b28-2b2a4a7d1f87", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import math" + ] + }, + { + "cell_type": "markdown", + "id": "01ded875-7782-42fe-ac05-33d9def0ae30", + "metadata": {}, + "source": [ + "## Finding transition weeks\n", + "\n", + "### What is a transition week?\n", + "Transition weeks are the starting and ending week of a phenophase, for example, the first and last weeks of a tree's 'budding flowers' period. We can think of transition weeks as the first week that the percent of observations observing an attribute (say budding flowers) exceeds some small threshold, such as 10%.\n", + "\n", + "### Our approach\n", + "We compute probability distributions for the start transition week and end transition week, $p_{start}$ and $p_{end}$, which range over all weeks in a year. We do this by normalizing score functions, $s_{start}$ and $s_{end}$, which are computed with respect to the plots of the percentage of observations each week of a species which observe a particular attribute.\n", + "\n", + "### Start transition score function\n", + "$s_{start}(w)$ is large for weeks $w$ which are likely to be a start transition week. Let $y(w)$ be the percentage of observations for a species which observe the attribute in week $w$. Then: \n", + "$$\n", + " s_{start}(w) = w_1 \\left(\\frac{1}{0.1 + \\left|\\max_{w - L \\leq k \\leq w} y(k) - \\min_{w - L \\leq k \\leq w} y(k)\\right|}\\right)^2 + w_2 (\\max\\left(y(w+M) - y(w), 0\\right))\n", + "$$\n", + "a weighted sum over two terms. The first term, which we refer to as stagnation, computes the difference between the maximum and minimum percentages from $L$ weeks before week $w$ through week $w$ and is large when this difference is small. The second term, which we refer to as spike, computes the change in percentage from week $w$ to week $w+M$. This term is large when there is a large increase in percentage from week $w$ to week $w+M$.\n", + "\n", + "### End transition score function\n", + "$s_{end}(w)$ is large for weeks $w$ which are likely to be an end transition week. Let $y(w)$ be the percentage of observations for a species which observe the attribute in week $w$. Then:\n", + "$$\n", + " s_{end}(w) = w_1 \\left(\\frac{1}{0.1 + \\left|\\max_{w \\leq k \\leq w+L} y(k) - \\min_{w \\leq k \\leq w+L} y(k)\\right|}\\right)^2 + w_2 (\\max\\left(y(w-M) - y(w), 0\\right))\n", + "$$\n", + "a weighted sum over two terms. In this case, the stagnation term, computes the difference between the maximum and minimum percentages from week $w$ through week $w+L$ and is large when this difference is small. The second term, which we refer to as spike, computes the change in percentage from week $w-M$ to week $w$. This term is large when there is a large decrease in percentage from week $w-M$ to week $w$.\n", + "\n", + "### Computing the probability distributions\n", + "We compute the distribution $p_{start}$, the probability that a given week is the start transition week, by normalizing the score function $s_{start}$.\n", + "\n", + "$$\n", + " p_{start}(w) = \\frac{s_{start}(w)}{\\sum_{w \\in Y} s_{start}(w)}\n", + "$$\n", + "\n", + "We compute the distribution $p_{end}$, the probability that a given week is the start transition week, by normalizing the score function $s_{end}$.\n", + "$$\n", + " p_{end}(w) = \\frac{s_{end}(w)}{\\sum_{w \\in Y} s_{end}(w)}\n", + "$$\n", + "\n", + "### Computing Means and Standard Deviations\n", + "Means and standard deviations are computed using the same process for $p_{start}$ and $p_{end}$. Let $w_{a}$ be a start week. We compute the mean start transition time $\\mu_{start}$ as follows:\n", + "$$\n", + " \\mu_{start} = \\sum_{w = w_a}^{w_a + 48}w \\cdot p_{start}(w)\n", + "$$\n", + "We compute the standard deviation of $p_{start}$, $\\sigma_{start}$ as follows:\n", + "$$\n", + " \\sigma_{start} = \\sqrt{\\sum_{w = w_a}^{w_a + 48}(w - \\mu_{start})^2 \\cdot p_{start}(w)}\n", + "$$\n", + "In practice, we center our 48 week window over which means and standard deviations are computed around local maxima in the score function. So $w_a$ ends up being 24 weeks before each local maximum.\n", + "\n", + "### Tuning Model Parameters $w_1, w_2, L, M$\n", + "Parameters to tune are $L$ (stagnation window size), $M$ (spike window size), $w_1$ (stagnation weight), and $w_2$ (spike weight). Our best practice currently is to set $w_2=1$, $w_1 = 0.25$, which reduces the amount stagnation factors into the score, ensuring that flat periods at the peaks of the percentage plot do not get high scores. We set $L=5$, which we found to be a reasonable number of weeks to look check back to identify periods of little change in the percentage plot of an attribute for a species. Most significantly, we set $M=15$, which we found to be a reasonable estimate of the number of weeks between the transition week and the peak week of the phenophase." + ] + }, + { + "cell_type": "markdown", + "id": "be4def9d-42f4-4020-ac35-fda5b0899212", + "metadata": {}, + "source": [ + "## Helper functions / data structures" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "7e1468cd-262c-42f7-aae2-9cb4c6e62643", + "metadata": {}, + "outputs": [], + "source": [ + "# load in name_to_id lookup table\n", + "name_to_id_df = pd.read_csv('../data/species codes.csv', encoding='unicode_escape')\n", + "id_to_name_dict = {}\n", + "for _, row in name_to_id_df.iterrows():\n", + " name = \"{}-{}\".format(row['species_primary_common_name'], row['species_scientific_name']).lower().replace(' ', '')\n", + " id_to_name_dict[row['species_id']] = name" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "e8a3bc4c-7adb-452b-bc20-3514ff860263", + "metadata": {}, + "outputs": [], + "source": [ + "'''\n", + "Helper function to get the percent of observations of a species which observe an attribute for each week of a given year\n", + "\n", + "Arguments:\n", + " state_df (pandas.DataFrame) - dataframe of observations for a given state\n", + " year (int) - year to compute percents for\n", + " species_id (int) - id of the species to compute percents for\n", + " attr (string) - attribute code. ex: 'Flowers_bud'\n", + "\n", + "Returns:\n", + " pcts (list) - a python list of percents of observations of a species which observe an attribute each week.\n", + " \n", + "'''\n", + "def get_percent_of_positive_observations_for_each_week(state_df, year, species_id, attr):\n", + " # filter state dataframe to get observations for just the species with id `species_id`\n", + " species_df = state_df[state_df['Species_id'] == species_id]\n", + "\n", + " # filter species_df to only observations in the given year\n", + " species_df = species_df[species_df['Year'] == year]\n", + "\n", + " # filter species_df to only rows where the attribute `attr` is observed to be 0 (none), 1 (some), 2 (many)\n", + " species_df = species_df[species_df[attr] >= 0]\n", + "\n", + " # initialize the array `pcts`\n", + " pcts = []\n", + " for week in range(48):\n", + " species_df_week = species_df[species_df['Week'] == week] # filter species_df to only data for the week\n", + " N = len(species_df_week)\n", + " if N == 0: # if this dataframe is empty, append 0 for percentage for that week\n", + " pcts.append(0)\n", + " #pcts.append(np.nan)\n", + " else:\n", + " species_df_week_observed = species_df_week[species_df_week[attr] > 0] # make dataframe of all observations for species which observe attr that week\n", + " pcts.append(len(species_df_week_observed) / N) # compute percentage of observations of species that week which observe attr, store in array `pcts`\n", + " return pcts # return the array of percents" + ] + }, + { + "cell_type": "markdown", + "id": "56ef05c0-349a-486c-814b-618f49c9dea5", + "metadata": { + "jp-MarkdownHeadingCollapsed": true + }, + "source": [ + "## Score Function" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "83227865-4a90-4b66-9ad5-c05c11da1840", + "metadata": {}, + "outputs": [], + "source": [ + "'''\n", + "Computes scores proportional to the likelihood that a certain week is a transition week for a given attribute\n", + "\n", + "Arguments:\n", + " state_df - dataframe of all observations for a given state. example: all citizen observations with State_name 'Kerala'\n", + " species_id - id of the species, an integer. example: 1161 (id of jackfruit)\n", + " attr - the categorical attribute to get scores for. example: 'Fruits_ripe'\n", + "\n", + "Parameters of Score function:\n", + " L (integer) - size of stagnation window. Stagnation is computed as the difference between the maximum and minimum percentages of observations of the attribute `attr` \n", + " L weeks before (if phenophase_start=True) or after (if phenophase_start=False) the current week\n", + " M (integer) - size of spike window. Spike is computed as the difference in percentage between the week M weeks after (if phenophase_start=True) or before (if phenophase_start=False) the current week, and the current week\n", + " w_1 (float) - weight of the stagnation term in the score function\n", + " w_2 (float) - weight of the spike term in the score function\n", + "\n", + "Configuration Options\n", + " phenophase_start (boolean) - If True, scores are computed for the transition at the start of the phenophase (allowing the distribution of transition times for the start transition week to be reconstructed). \n", + " If False, scores are computed for the transition at the end of the phenophase (allowing the distribution of transition times for the end transition week to be reconstructed).\n", + "'''\n", + "def get_scores(state_df, species_id, attr, L=5, M=15, w_1=0.25, w_2=1, phenophase_start=True):\n", + " # compute percentage of observations of species `species_id` which observe attribute `attr` present for each week of the year, for 2018 through 2024\n", + " pcts = np.array([])\n", + " for year in range(2018, 2024):\n", + " pcts = np.concatenate([pcts, get_percent_of_positive_observations_for_each_week(state_df, year, species_id, attr)], axis = 0)\n", + " weeks = np.arange(48)\n", + " \n", + " # compute scores for start transition week distribution\n", + " if phenophase_start:\n", + "\n", + " # compile arrays of scores, stagnations, and spikes for each week\n", + " scores = []\n", + " stagns = []\n", + " spikes = []\n", + " for week in range(L, len(pcts)-M, 1): # loop through all weeks of percents, except weeks where computing score would overflow through one end of the array `pcts`\n", + " stagn = (1 / (0.1 + np.max(pcts[week-L:week+1]) - np.min(pcts[week-L:week+1])))**2 # compute stagnation term. Note that the 0.1 in denominator of `stagn` makes the 100 the maximum possible value of `stagn`.\n", + " spike = max(0, pcts[week + M] - pcts[week]) # compute spike term\n", + " spikes.append(spike)\n", + " stagns.append(stagn)\n", + " spikes = 100 * (np.array(spikes) - np.min(spikes)) / np.max(spikes) # normalize array of spikes to be between zero and 100\n", + " scores = (w_1 * np.array(stagns) + w_2 * np.array(spikes)) / 2 # take score array to be average of stagnation and spike arrays\n", + "\n", + " # add zeros for padding to end of spikes, stagnations, and scores arrays to have a score for each week of the year\n", + " spikes = np.array([0 for i in range(L)] + list(spikes) + [0 for i in range(M)]) \n", + " stagns = np.array([0 for i in range(L)] + list(stagns) + [0 for i in range(M)])\n", + " scores = np.array([0 for i in range(L)] + list(scores) + [0 for i in range(M)])\n", + "\n", + " # return the score, spike, and stagnation arrays\n", + " return scores, spikes, stagns\n", + "\n", + " # otherwise, compute scores for end transition week distribution\n", + " else:\n", + " # compile arrays of scores, stagnations, and spikes for each week\n", + " scores = []\n", + " stagns = []\n", + " spikes = []\n", + " for week in range(M, len(pcts)-L, 1): # loop through all weeks of percents, except weeks where computing score would overflow through one end of the array `pcts`\n", + " stagn = (1 / (0.1 + max(pcts[week:week+L+1]) - min(pcts[week:week+L+1])))**2 # compute stagnation term. Note that the 0.1 in denominator of `stagn` makes the 100 the maximum possible value of `stagn`\n", + " spike = max(0, pcts[week - M] - pcts[week]) # compute spike term\n", + " spikes.append(spike)\n", + " stagns.append(stagn)\n", + " spikes = 100 * (np.array(spikes) - np.min(spikes)) / np.max(spikes) # normalize array of spikes to be between zero and 100\n", + " scores = (w_1 * np.array(stagns) + w_2 * np.array(spikes)) / 2 # take score array to be average of stagnation and spike arrays\n", + "\n", + " # add zeros for padding to end of spikes, stagnations, and scores arrays to have a score for each week of the year\n", + " spikes = np.array([0 for i in range(L)] + list(spikes) + [0 for i in range(M)])\n", + " stagns = np.array([0 for i in range(L)] + list(stagns) + [0 for i in range(M)])\n", + " scores = np.array([0 for i in range(L)] + list(scores) + [0 for i in range(M)])\n", + "\n", + " # return the score, spike, and stagnation arrays\n", + " return scores, spikes, stagns" + ] + }, + { + "cell_type": "markdown", + "id": "bbbb9217-6535-45f8-ba89-1b8c4ca8def2", + "metadata": {}, + "source": [ + "## Compile and Plot Dataset of Mean Transition Times" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "11a5392f-f926-409f-adbe-77fb381bebbb", + "metadata": {}, + "outputs": [], + "source": [ + "'''\n", + " Given a dataframe for a state, a species_id, and an attribute, compute the mean and standard deviation transition times for each year\n", + " for that attribute. Return a dataframe with the information\n", + "'''\n", + "def get_means_and_stds(state_df, species_id, attr, L=5, M=3, w_1=1, w_2=1, w_3=1, state_name='Kerala'):\n", + " scores_start, spikes_start, stagns_start = get_scores(state_df, species_id, attr, L=L, M=M, w_1=w_1, w_2=w_2,\n", + " phenophase_start=True)\n", + " scores_end, spikes_end, stagns_end = get_scores(state_df, species_id, attr, L=L, M=M, w_1=w_1, w_2=w_2,\n", + " phenophase_start=False)\n", + " start_means = []\n", + " mean_start_probs = []\n", + " end_means = []\n", + " mean_end_probs = []\n", + " max_prob_start_weeks = []\n", + " max_start_probs = []\n", + " max_prob_end_weeks = []\n", + " max_end_probs = []\n", + "\n", + " for i in range(0,len(scores_start), 48):\n", + "\n", + " score_for_year = scores_start[range(i, i+48)].copy() # get scores for year\n", + " \n", + " max_start_score_idx = i+24 + np.argmax(scores_start[i+24:i+48]) # find max score in second half of year, will become 'center' of the start transition week distribution for year\n", + " max_end_score_idx = i + np.argmax(scores_end[i:i+24]) # find max score in first half of year of year, will become 'center' of the end transition week distribution for year\n", + "\n", + " start_range = range(max(0, max_start_score_idx-24), min(len(scores_start), max_start_score_idx+24)) # compute week indices of the scores in the start distribution\n", + " end_range = range(max(0, max_end_score_idx-24), min(len(scores_start), max_end_score_idx+24)) # compute week indices of scores in the ending distribution\n", + " \n", + " start_scores_for_year = scores_start[start_range].copy() # get all scores in window around center of start distribution\n", + " end_scores_for_year = scores_end[end_range].copy() # get all scores in window around center of end distribution\n", + " \n", + " start_probs_for_year = start_scores_for_year / np.sum(start_scores_for_year) # normalize scores in start window to get probabilities\n", + " end_probs_for_year = end_scores_for_year / np.sum(end_scores_for_year) # normalize scores in end window to get probabilities\n", + " \n", + " start_mean = np.dot(start_probs_for_year, start_range) # compute mean of start transition week distribution\n", + " end_mean = np.dot(end_probs_for_year, end_range) # compute mean of ending transition week distribution\n", + " start_means.append(round(start_mean % 48)) # add means to array, taking modulo 48 to convert to a week of the year, and rounding to nearest integer\n", + " end_means.append(round(end_mean % 48)) \n", + "\n", + " mean_start_probs.append((score_for_year / np.sum(start_scores_for_year))[round(start_mean % 48)])\n", + " mean_end_probs.append((score_for_year / np.sum(end_scores_for_year))[round(end_mean % 48)])\n", + " \n", + " max_start_prob = np.max(start_probs_for_year) # compute maximum probability start week\n", + " max_start_probs.append(max_start_prob)\n", + " max_end_prob = np.max(end_probs_for_year) # compute maximum probability end week \n", + " max_end_probs.append(max_end_prob) \n", + "\n", + "\n", + " max_start_week = round(max_start_score_idx % 48) # compute maximum probability start week, round to nearest integer\n", + " max_end_week = round(max_end_score_idx % 48) # compute maximum probability end week, round to nearest integer\n", + " max_prob_start_weeks.append(max_start_week)\n", + " max_prob_end_weeks.append(max_end_week)\n", + "\n", + " \"\"\"\n", + " Colum name | Description\n", + "\n", + " Species name | name of the species \n", + "\n", + " Year | year of observation\n", + "\n", + " Phenophase | plant stage of observation\n", + "\n", + " Phenophase start week | week with maximum probability for initiation\n", + "\n", + " Max Initiation probability | max probability value for initiation\n", + "\n", + " Mean initiation week | week with average probability for initiation\n", + "\n", + " Mean initiation probability | mean probability value of initiation\n", + "\n", + " Phenophase end week | week with maximum probability for end\n", + "\n", + " Max end probability | max probability value for end\n", + "\n", + " Mean end week | week with average probability for end\n", + "\n", + " Mean end probability | mean probability value for end\n", + " \"\"\"\n", + "\n", + " # Create an empty dataframe to store transition times\n", + " transition_time_df = pd.DataFrame(columns=['Species_name', 'Year', 'Phenophase', 'Phenophase_start_week',\n", + " 'Max_Initiation_probability', 'Mean_initiation_week', 'Mean_initiation_probability', 'Phenophase_end_week', 'Max_end_probability', 'Mean_end_week', 'Mean_end_probability'])\n", + "\n", + " # get species and state name\n", + " species_name = state_df[state_df['Species_id'] == species_id].iloc[0]['Species_name']\n", + " \n", + " # add all mean transition times and stds to dataframe and return the filled dataframe\n", + " # one row for each year\n", + " years = list(range(2018, 2024))\n", + " for start_mean_prob, end_mean_prob, start_mean, end_mean, start_prob, end_prob, max_start, max_end, year in zip(mean_start_probs, mean_end_probs, start_means, end_means, max_start_probs, max_end_probs, max_prob_start_weeks, max_prob_end_weeks, years):\n", + " transition_time_df.loc[len(transition_time_df)] = {'Species_name': species_name,'Year': year, 'Phenophase': attr, \n", + " 'Phenophase_start_week': max_start, 'Max_Initiation_probability': start_prob,\n", + " 'Mean_initiation_week': start_mean, 'Mean_initiation_probability': start_mean_prob, \n", + " 'Phenophase_end_week': max_end, 'Max_end_probability': end_prob,\n", + " 'Mean_end_week': end_mean, 'Mean_end_probability': end_mean_prob}\n", + " # print(transition_time_df)\n", + " return transition_time_df" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "8e0c3f0b-7261-4647-8803-c5bfee58deda", + "metadata": {}, + "outputs": [], + "source": [ + "# initialize dictionary which maps species id to a list of all attributes for which we will compute mean transition times for that species id \n", + "transition_dict = {1090: ['Flowers_bud', 'Flowers_open', 'Fruits_unripe', 'Fruits_ripe'],\n", + " 1161: ['Flowers_bud', 'Flowers_male', 'Flowers_Female', 'Fruits_unripe', 'Fruits_ripe']}\n", + "\n", + "# load all kerala observations\n", + "kerala_df = pd.read_csv('../data/citizen_states_cleaned/kerala.csv')\n", + "\n", + "# get a list of all transition dataframes, for each species_id / attr pair in transition_dict\n", + "transition_dfs = []\n", + "transition_time_df_mango_jack = []\n", + "for species_id in list(transition_dict.keys()):\n", + " for attr in transition_dict[species_id]:\n", + " transition_dfs.append(get_means_and_stds(kerala_df, species_id, attr, M=15, L=5, w_1=0.25, w_2=1)) # add transition dataframe for species / attr to the list\n", + "\n", + "# join all dataframes together\n", + "transition_time_df_mango_jack = pd.concat(transition_dfs, ignore_index=True)\n", + "transition_time_df_mango_jack.to_csv(\"../data/year_to_year_transition_time.csv\", index=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "82df1381", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + " | Species_name | \n", + "Year | \n", + "Phenophase | \n", + "Phenophase_start_week | \n", + "Max_Initiation_probability | \n", + "Mean_initiation_week | \n", + "Mean_initiation_probability | \n", + "Phenophase_end_week | \n", + "Max_end_probability | \n", + "Mean_end_week | \n", + "Mean_end_probability | \n", + "
---|---|---|---|---|---|---|---|---|---|---|---|
0 | \n", + "Mango (all varieties)-Mangifera indica | \n", + "2018 | \n", + "Flowers_bud | \n", + "37 | \n", + "0.080885 | \n", + "32 | \n", + "0.042795 | \n", + "9 | \n", + "0.078031 | \n", + "14 | \n", + "0.001249 | \n", + "
1 | \n", + "Mango (all varieties)-Mangifera indica | \n", + "2019 | \n", + "Flowers_bud | \n", + "33 | \n", + "0.083946 | \n", + "32 | \n", + "0.061640 | \n", + "11 | \n", + "0.087274 | \n", + "12 | \n", + "0.001240 | \n", + "
2 | \n", + "Mango (all varieties)-Mangifera indica | \n", + "2020 | \n", + "Flowers_bud | \n", + "33 | \n", + "0.074016 | \n", + "30 | \n", + "0.063177 | \n", + "8 | \n", + "0.073567 | \n", + "8 | \n", + "0.000944 | \n", + "
3 | \n", + "Mango (all varieties)-Mangifera indica | \n", + "2021 | \n", + "Flowers_bud | \n", + "35 | \n", + "0.071315 | \n", + "37 | \n", + "0.070194 | \n", + "9 | \n", + "0.063489 | \n", + "9 | \n", + "0.001954 | \n", + "
4 | \n", + "Mango (all varieties)-Mangifera indica | \n", + "2022 | \n", + "Flowers_bud | \n", + "34 | \n", + "0.064178 | \n", + "32 | \n", + "0.056300 | \n", + "11 | \n", + "0.068561 | \n", + "11 | \n", + "0.003461 | \n", + "
5 | \n", + "Mango (all varieties)-Mangifera indica | \n", + "2023 | \n", + "Flowers_bud | \n", + "27 | \n", + "0.135528 | \n", + "20 | \n", + "0.017784 | \n", + "8 | \n", + "0.074932 | \n", + "9 | \n", + "0.002084 | \n", + "
6 | \n", + "Mango (all varieties)-Mangifera indica | \n", + "2018 | \n", + "Flowers_open | \n", + "33 | \n", + "0.078766 | \n", + "32 | \n", + "0.043086 | \n", + "9 | \n", + "0.073493 | \n", + "14 | \n", + "0.001177 | \n", + "
7 | \n", + "Mango (all varieties)-Mangifera indica | \n", + "2019 | \n", + "Flowers_open | \n", + "36 | \n", + "0.081853 | \n", + "34 | \n", + "0.076870 | \n", + "11 | \n", + "0.084680 | \n", + "13 | \n", + "0.000676 | \n", + "
8 | \n", + "Mango (all varieties)-Mangifera indica | \n", + "2020 | \n", + "Flowers_open | \n", + "33 | \n", + "0.071209 | \n", + "31 | \n", + "0.059765 | \n", + "8 | \n", + "0.073729 | \n", + "9 | \n", + "0.004183 | \n", + "
9 | \n", + "Mango (all varieties)-Mangifera indica | \n", + "2021 | \n", + "Flowers_open | \n", + "37 | \n", + "0.066565 | \n", + "38 | \n", + "0.064819 | \n", + "7 | \n", + "0.063056 | \n", + "10 | \n", + "0.001382 | \n", + "
10 | \n", + "Mango (all varieties)-Mangifera indica | \n", + "2022 | \n", + "Flowers_open | \n", + "36 | \n", + "0.062426 | \n", + "33 | \n", + "0.053500 | \n", + "10 | \n", + "0.065309 | \n", + "11 | \n", + "0.004448 | \n", + "
11 | \n", + "Mango (all varieties)-Mangifera indica | \n", + "2023 | \n", + "Flowers_open | \n", + "27 | \n", + "0.117441 | \n", + "20 | \n", + "0.052029 | \n", + "9 | \n", + "0.075369 | \n", + "10 | \n", + "0.001952 | \n", + "
12 | \n", + "Mango (all varieties)-Mangifera indica | \n", + "2018 | \n", + "Fruits_unripe | \n", + "47 | \n", + "0.052219 | \n", + "39 | \n", + "0.028195 | \n", + "17 | \n", + "0.077346 | \n", + "17 | \n", + "0.001203 | \n", + "
13 | \n", + "Mango (all varieties)-Mangifera indica | \n", + "2019 | \n", + "Fruits_unripe | \n", + "36 | \n", + "0.076008 | \n", + "37 | \n", + "0.047013 | \n", + "19 | \n", + "0.071626 | \n", + "20 | \n", + "0.002506 | \n", + "
14 | \n", + "Mango (all varieties)-Mangifera indica | \n", + "2020 | \n", + "Fruits_unripe | \n", + "33 | \n", + "0.057263 | \n", + "37 | \n", + "0.053314 | \n", + "17 | \n", + "0.082137 | \n", + "15 | \n", + "0.001287 | \n", + "
15 | \n", + "Mango (all varieties)-Mangifera indica | \n", + "2021 | \n", + "Fruits_unripe | \n", + "46 | \n", + "0.073978 | \n", + "41 | \n", + "0.055299 | \n", + "15 | \n", + "0.071094 | \n", + "17 | \n", + "0.002096 | \n", + "
16 | \n", + "Mango (all varieties)-Mangifera indica | \n", + "2022 | \n", + "Fruits_unripe | \n", + "37 | \n", + "0.058415 | \n", + "38 | \n", + "0.044795 | \n", + "18 | \n", + "0.081649 | \n", + "18 | \n", + "0.001720 | \n", + "
17 | \n", + "Mango (all varieties)-Mangifera indica | \n", + "2023 | \n", + "Fruits_unripe | \n", + "32 | \n", + "0.089417 | \n", + "18 | \n", + "0.015173 | \n", + "17 | \n", + "0.070176 | \n", + "17 | \n", + "0.005408 | \n", + "
18 | \n", + "Mango (all varieties)-Mangifera indica | \n", + "2018 | \n", + "Fruits_ripe | \n", + "47 | \n", + "0.076606 | \n", + "46 | \n", + "0.023212 | \n", + "21 | \n", + "0.096492 | \n", + "23 | \n", + "0.001517 | \n", + "
19 | \n", + "Mango (all varieties)-Mangifera indica | \n", + "2019 | \n", + "Fruits_ripe | \n", + "47 | \n", + "0.094701 | \n", + "45 | \n", + "0.068007 | \n", + "23 | \n", + "0.095571 | \n", + "24 | \n", + "0.002291 | \n", + "
20 | \n", + "Mango (all varieties)-Mangifera indica | \n", + "2020 | \n", + "Fruits_ripe | \n", + "44 | \n", + "0.060988 | \n", + "44 | \n", + "0.060988 | \n", + "20 | \n", + "0.096430 | \n", + "22 | \n", + "0.000306 | \n", + "
21 | \n", + "Mango (all varieties)-Mangifera indica | \n", + "2021 | \n", + "Fruits_ripe | \n", + "46 | \n", + "0.080569 | \n", + "45 | \n", + "0.071703 | \n", + "23 | \n", + "0.074820 | \n", + "23 | \n", + "0.001344 | \n", + "
22 | \n", + "Mango (all varieties)-Mangifera indica | \n", + "2022 | \n", + "Fruits_ripe | \n", + "45 | \n", + "0.070768 | \n", + "43 | \n", + "0.052355 | \n", + "18 | \n", + "0.089201 | \n", + "21 | \n", + "0.001908 | \n", + "
23 | \n", + "Mango (all varieties)-Mangifera indica | \n", + "2023 | \n", + "Fruits_ripe | \n", + "29 | \n", + "0.237421 | \n", + "14 | \n", + "0.020433 | \n", + "18 | \n", + "0.083384 | \n", + "20 | \n", + "0.002801 | \n", + "
24 | \n", + "Jackfruit-Artocarpus heterophyllus | \n", + "2018 | \n", + "Flowers_bud | \n", + "39 | \n", + "0.093836 | \n", + "29 | \n", + "0.008736 | \n", + "7 | \n", + "0.162487 | \n", + "11 | \n", + "0.066572 | \n", + "
25 | \n", + "Jackfruit-Artocarpus heterophyllus | \n", + "2019 | \n", + "Flowers_bud | \n", + "30 | \n", + "0.094985 | \n", + "31 | \n", + "0.070626 | \n", + "11 | \n", + "0.080737 | \n", + "12 | \n", + "0.002815 | \n", + "
26 | \n", + "Jackfruit-Artocarpus heterophyllus | \n", + "2020 | \n", + "Flowers_bud | \n", + "32 | \n", + "0.069781 | \n", + "30 | \n", + "0.067125 | \n", + "2 | \n", + "0.078175 | \n", + "7 | \n", + "0.000516 | \n", + "
27 | \n", + "Jackfruit-Artocarpus heterophyllus | \n", + "2021 | \n", + "Flowers_bud | \n", + "24 | \n", + "0.088931 | \n", + "33 | \n", + "0.013553 | \n", + "9 | \n", + "0.060094 | \n", + "10 | \n", + "0.001900 | \n", + "
28 | \n", + "Jackfruit-Artocarpus heterophyllus | \n", + "2022 | \n", + "Flowers_bud | \n", + "33 | \n", + "0.065010 | \n", + "33 | \n", + "0.065010 | \n", + "11 | \n", + "0.081440 | \n", + "13 | \n", + "0.005259 | \n", + "
29 | \n", + "Jackfruit-Artocarpus heterophyllus | \n", + "2023 | \n", + "Flowers_bud | \n", + "28 | \n", + "0.083798 | \n", + "20 | \n", + "0.015034 | \n", + "5 | \n", + "0.075517 | \n", + "8 | \n", + "0.007393 | \n", + "
30 | \n", + "Jackfruit-Artocarpus heterophyllus | \n", + "2018 | \n", + "Flowers_male | \n", + "39 | \n", + "0.081075 | \n", + "31 | \n", + "0.033073 | \n", + "8 | \n", + "0.169346 | \n", + "12 | \n", + "0.058593 | \n", + "
31 | \n", + "Jackfruit-Artocarpus heterophyllus | \n", + "2019 | \n", + "Flowers_male | \n", + "30 | \n", + "0.092313 | \n", + "32 | \n", + "0.072979 | \n", + "11 | \n", + "0.077068 | \n", + "12 | \n", + "0.002162 | \n", + "
32 | \n", + "Jackfruit-Artocarpus heterophyllus | \n", + "2020 | \n", + "Flowers_male | \n", + "31 | \n", + "0.072172 | \n", + "30 | \n", + "0.068216 | \n", + "7 | \n", + "0.080879 | \n", + "8 | \n", + "0.000526 | \n", + "
33 | \n", + "Jackfruit-Artocarpus heterophyllus | \n", + "2021 | \n", + "Flowers_male | \n", + "24 | \n", + "0.088873 | \n", + "32 | \n", + "0.004362 | \n", + "9 | \n", + "0.059481 | \n", + "10 | \n", + "0.002164 | \n", + "
34 | \n", + "Jackfruit-Artocarpus heterophyllus | \n", + "2022 | \n", + "Flowers_male | \n", + "33 | \n", + "0.065127 | \n", + "33 | \n", + "0.065127 | \n", + "11 | \n", + "0.107912 | \n", + "12 | \n", + "0.005845 | \n", + "
35 | \n", + "Jackfruit-Artocarpus heterophyllus | \n", + "2023 | \n", + "Flowers_male | \n", + "27 | \n", + "0.112685 | \n", + "20 | \n", + "0.059000 | \n", + "5 | \n", + "0.080065 | \n", + "8 | \n", + "0.006755 | \n", + "
36 | \n", + "Jackfruit-Artocarpus heterophyllus | \n", + "2018 | \n", + "Flowers_Female | \n", + "39 | \n", + "0.083802 | \n", + "31 | \n", + "0.027218 | \n", + "7 | \n", + "0.169025 | \n", + "12 | \n", + "0.052369 | \n", + "
37 | \n", + "Jackfruit-Artocarpus heterophyllus | \n", + "2019 | \n", + "Flowers_Female | \n", + "30 | \n", + "0.088315 | \n", + "31 | \n", + "0.072415 | \n", + "11 | \n", + "0.077781 | \n", + "12 | \n", + "0.002269 | \n", + "
38 | \n", + "Jackfruit-Artocarpus heterophyllus | \n", + "2020 | \n", + "Flowers_Female | \n", + "32 | \n", + "0.073765 | \n", + "30 | \n", + "0.066622 | \n", + "7 | \n", + "0.078331 | \n", + "8 | \n", + "0.000514 | \n", + "
39 | \n", + "Jackfruit-Artocarpus heterophyllus | \n", + "2021 | \n", + "Flowers_Female | \n", + "24 | \n", + "0.092301 | \n", + "32 | \n", + "0.005895 | \n", + "9 | \n", + "0.061022 | \n", + "10 | \n", + "0.002119 | \n", + "
40 | \n", + "Jackfruit-Artocarpus heterophyllus | \n", + "2022 | \n", + "Flowers_Female | \n", + "33 | \n", + "0.069215 | \n", + "33 | \n", + "0.069215 | \n", + "11 | \n", + "0.087135 | \n", + "13 | \n", + "0.005407 | \n", + "
41 | \n", + "Jackfruit-Artocarpus heterophyllus | \n", + "2023 | \n", + "Flowers_Female | \n", + "26 | \n", + "0.100049 | \n", + "20 | \n", + "0.028444 | \n", + "5 | \n", + "0.078719 | \n", + "8 | \n", + "0.006328 | \n", + "
42 | \n", + "Jackfruit-Artocarpus heterophyllus | \n", + "2018 | \n", + "Fruits_unripe | \n", + "47 | \n", + "0.064519 | \n", + "43 | \n", + "0.064160 | \n", + "16 | \n", + "0.078017 | \n", + "23 | \n", + "0.010342 | \n", + "
43 | \n", + "Jackfruit-Artocarpus heterophyllus | \n", + "2019 | \n", + "Fruits_unripe | \n", + "35 | \n", + "0.082230 | \n", + "35 | \n", + "0.082230 | \n", + "20 | \n", + "0.073110 | \n", + "22 | \n", + "0.002942 | \n", + "
44 | \n", + "Jackfruit-Artocarpus heterophyllus | \n", + "2020 | \n", + "Fruits_unripe | \n", + "33 | \n", + "0.066633 | \n", + "35 | \n", + "0.061382 | \n", + "13 | \n", + "0.072977 | \n", + "14 | \n", + "0.002507 | \n", + "
45 | \n", + "Jackfruit-Artocarpus heterophyllus | \n", + "2021 | \n", + "Fruits_unripe | \n", + "46 | \n", + "0.066491 | \n", + "40 | \n", + "0.016771 | \n", + "17 | \n", + "0.070198 | \n", + "17 | \n", + "0.001412 | \n", + "
46 | \n", + "Jackfruit-Artocarpus heterophyllus | \n", + "2022 | \n", + "Fruits_unripe | \n", + "36 | \n", + "0.065657 | \n", + "38 | \n", + "0.046096 | \n", + "18 | \n", + "0.069877 | \n", + "19 | \n", + "0.006932 | \n", + "
47 | \n", + "Jackfruit-Artocarpus heterophyllus | \n", + "2023 | \n", + "Fruits_unripe | \n", + "32 | \n", + "0.099631 | \n", + "20 | \n", + "0.008839 | \n", + "21 | \n", + "0.067196 | \n", + "19 | \n", + "0.001269 | \n", + "
48 | \n", + "Jackfruit-Artocarpus heterophyllus | \n", + "2018 | \n", + "Fruits_ripe | \n", + "47 | \n", + "0.108633 | \n", + "3 | \n", + "0.000000 | \n", + "17 | \n", + "0.092534 | \n", + "30 | \n", + "0.014529 | \n", + "
49 | \n", + "Jackfruit-Artocarpus heterophyllus | \n", + "2019 | \n", + "Fruits_ripe | \n", + "46 | \n", + "0.073263 | \n", + "46 | \n", + "0.073263 | \n", + "23 | \n", + "0.089464 | \n", + "26 | \n", + "0.002005 | \n", + "
50 | \n", + "Jackfruit-Artocarpus heterophyllus | \n", + "2020 | \n", + "Fruits_ripe | \n", + "45 | \n", + "0.054720 | \n", + "45 | \n", + "0.054720 | \n", + "20 | \n", + "0.095168 | \n", + "23 | \n", + "0.000681 | \n", + "
51 | \n", + "Jackfruit-Artocarpus heterophyllus | \n", + "2021 | \n", + "Fruits_ripe | \n", + "46 | \n", + "0.061518 | \n", + "46 | \n", + "0.061518 | \n", + "23 | \n", + "0.079191 | \n", + "23 | \n", + "0.003415 | \n", + "
52 | \n", + "Jackfruit-Artocarpus heterophyllus | \n", + "2022 | \n", + "Fruits_ripe | \n", + "45 | \n", + "0.068468 | \n", + "45 | \n", + "0.068468 | \n", + "23 | \n", + "0.078928 | \n", + "24 | \n", + "0.001987 | \n", + "
53 | \n", + "Jackfruit-Artocarpus heterophyllus | \n", + "2023 | \n", + "Fruits_ripe | \n", + "32 | \n", + "0.141372 | \n", + "18 | \n", + "0.057136 | \n", + "21 | \n", + "0.076214 | \n", + "22 | \n", + "0.005497 | \n", + "