<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Luke Gloege, Ph.D. on Medium]]></title>
        <description><![CDATA[Stories by Luke Gloege, Ph.D. on Medium]]></description>
        <link>https://medium.com/@lukegloege?source=rss-780866762df------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*-N5oAyTHrtvtuZVpDuRfBg.jpeg</url>
            <title>Stories by Luke Gloege, Ph.D. on Medium</title>
            <link>https://medium.com/@lukegloege?source=rss-780866762df------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 03:40:32 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@lukegloege/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Analyzing Lake Mendota Ice Phenology with Python]]></title>
            <link>https://medium.com/data-science/analyzing-lake-mendota-ice-phenology-withpython-03f2e95ac4bc?source=rss-780866762df------2</link>
            <guid isPermaLink="false">https://medium.com/p/03f2e95ac4bc</guid>
            <category><![CDATA[python]]></category>
            <category><![CDATA[pandas]]></category>
            <category><![CDATA[climate-change]]></category>
            <category><![CDATA[climate-data-science]]></category>
            <category><![CDATA[hands-on-tutorials]]></category>
            <dc:creator><![CDATA[Luke Gloege, Ph.D.]]></dc:creator>
            <pubDate>Mon, 10 Jun 2024 18:24:09 GMT</pubDate>
            <atom:updated>2024-06-10T20:20:27.758Z</atom:updated>
            <content:encoded><![CDATA[<h4>An analysis-ready dataset you can use right now</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ShZKJc4ubASGlit3" /><figcaption>Photo by <a href="https://unsplash.com/@iamthedave?utm_source=medium&amp;utm_medium=referral">Dave Hoefler</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Lake Mendota in Madison Wisconsin is arguably one of the <a href="https://integrativebiology.wisc.edu/2022/03/22/lake-mendota-said-to-be-most-studied-lake-in-the-world/">most-studied</a> lakes in the world and boasts an ice phenology record dating back to the 1850’s. The Climatology Office at the University of Wisconsin, which currently maintains the record, has made amazing strides to make the data <a href="https://climatology.nelson.wisc.edu/first-order-station-climate-data/madison-climate/lake-ice/history-of-ice-freezing-and-thawing-on-lake-mendota/">downloadable</a>. However, a simple plot of the ice-on date over time still requires some work by the user to make it legible. This is the reason I created <a href="https://mendotapy.readthedocs.io/en/latest/"><em>mendotapy</em></a><em>, </em>an open source (MIT-licensed) Python package that allows retrieval of this valuable data in an analysis-ready format. It has some built-in utilities to make common transformations a breeze.</p><p>I find this dataset interesting from a climate perspective, but I think anybody can find this dataset relevant. For instance, it could be used as a toy dataset to practice regression analysis, statistical forecasting, or spectral analysis.</p><p>In this post, I will describe the package, present some plots of the data, and conclude with the challenges I faced making this package. All the code to generate the figures is available in this post and the package code is available on <a href="https://github.com/lgloege/mendotapy">GitHub</a> (contributions are welcome)</p><h3>Installation and using the package</h3><p>First, you will need to install the package via pip.</p><pre>pip install mendotapy</pre><p>Once installed, it only takes two lines to retrieve an analysis-ready copy of Lake Mendota’s ice phenology returned as a Pandas DataFrame.</p><pre>import mendotapy<br>df = mendotapy.retrieve_data()</pre><p>The beauty of this is all the pre-processing has been done for you so you can you jump right into analysis! Figure 1 shows a screenshot of the dataframe.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ZkbvdHlTbOxOmtZgvxh2iQ.jpeg" /><figcaption>Figure 1. screenshot of dataframe (image by author)</figcaption></figure><p>You will notice that some seasons are duplicated (e.g. 2018–19). These indicate years where the lake thawed twice in a single season. However, the climatology office only provides the duration for the most recent freeze/thaw cycle in the season.</p><p>I have also added latitude , longitude , lakename , and lakecode to the dataset for consistency with the National Snow and Ice Data Center’s (NSIDC) <a href="https://nsidc.org/data/lake_river_ice/">Global Lake and Rive Ice Phenology</a>. The latter is a great resource for ice phenology data from over 500 lakes and rivers. Although, at the time of writing, the database contains a temporal coverage up to February 2nd, 2018. mendotapy gives you access to the most recent data for Lake Mendota.</p><h3>Dataframe Accessor</h3><p>What if you just want the start of the season? For instance, the season is 2023-24 but you just want 2023? Or what if you want the day of the year (DOY) for the ice on date? To facilitate this sort of functionality, a dataframe accessor was added to the package.</p><p>This additional functionality is within an accessor named utils</p><pre>df.utils.season_start() # returns list of the start of the season<br><br>df.utils.iceon_doy()    # returns list of ice on day of year<br>df.utils.iceoff_doy()   # returns list of ice off day of year</pre><p>This makes plotting the ice on DOY like this:</p><pre>import matplotlib.pyplot as plt<br><br>season_start = df.utils.season_start()<br>iceon_doy = df.utils.iceon_doy()<br><br>plt.plot(season_start, iceon_doy)</pre><p>Which produces a the graph shown in Figure 2.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PlVhn83mDKJDZvWt-sWhfw.png" /><figcaption>Figure 2: ice-on day of year versus time (image by author)</figcaption></figure><p>Why does this look so spiky? In most seasons, the ice-on date happens in December, but sometimes it occurs in January if the summer is unusually warm that season. One way to improve this plot is to “wrap” the DOY values into the following year. In other words, add 365 to DOYs occurring in January. to do this in mendotapy , simply use the df.utils.iceon_doy_wrapped() method. So the new code looks like</p><pre>import matplotlib.pyplot as plt<br><br>season_start = df.utils.season_start()<br>iceon_doy_wrapped = df.utils.iceon_doy_wrapped()<br><br>plt.plot(season_start, iceon_doy_wrapped)</pre><p>Applying this trick, we can clearly see the upward trend in the data</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*b3CV06YcNiUizi1fy7uD0A.png" /><figcaption>ice-on day of year versus time. In this figure, days of the year greater than 365 or 366 indicate the following year (image by author)</figcaption></figure><h3>How to make an accessor</h3><p>Making an <a href="https://pandas.pydata.org/docs/reference/api/pandas.api.extensions.register_dataframe_accessor.html">accessor</a> like this was new for me and opens up a whole world of possibilities with Pandas (and Xarray too!). Let’s dive into how these accessors work.</p><p>My approach was to start with a function I know works on a DataFrame. For instance, the function below will return a list with the start of the season.</p><pre>def season_start(df):<br>        &quot;&quot;&quot;start of the season<br><br>        Returns<br>        -------<br>            list: start of the season.<br>                  e.g., the 2023-2024 winter season return 2023<br>        &quot;&quot;&quot;<br>        season_list = []<br>        for index, row in df.iterrows():<br>            season = int(row[&quot;season&quot;].split(&quot;-&quot;)[0])<br>            season_list.append(season)<br>        return season_list</pre><p>You can then apply the function to a DataFrame.</p><pre>import mendotapy<br><br># load raw data<br>df = mendotapy.load()<br><br># apply season_start function<br>start_of_season = season_start(df)</pre><p>To turn this into an accessor and use the function like this df.utils.seasons_start() we need to to take the following steps:</p><ol><li>Make the function into a class method (class name is irrelevant)</li><li>Initialize the class with a DataFrame</li><li>Decorate the function to register an accessor</li></ol><p>Here is the code that makes this work in mendotapy</p><pre>@pd.api.extensions.register_dataframe_accessor(&quot;utils&quot;)<br>class MendotapyAccessor:<br>    def __init__(self, df):<br>        self._df = df<br><br>    def season_start(self):<br>        &quot;&quot;&quot;start of the season<br><br>        Returns<br>        -------<br>            list: start of the season.<br>                  e.g., the 2023-2024 winter season return 2023<br>        &quot;&quot;&quot;<br>        season_list = []<br>        for index, row in self._df.iterrows():<br>            season = int(row[&quot;season&quot;].split(&quot;-&quot;)[0])<br>            season_list.append(season)<br>        return season_list</pre><p>The secret sauce that makes all this work is the decorator. The name you pass as an argument will be the name of the accessor.</p><pre>@pd.api.extensions.register_dataframe_accessor(&quot;utils&quot;)</pre><p>Accessors are great for grouping related methods or properties together. However, for mendotapy I took the naive route and dumped all the methods into a single accessor named utils .</p><h3>Plotting and Analyzing the Data</h3><p>This is a fun dataset to play around with and one of my go-to-datasets when explaining climate change and global warming. It was even featured in a 2018 data visualization battle (<a href="https://www.reddit.com/r/dataisbeautiful/comments/a2p5f0/battle_dataviz_battle_for_the_month_of_december/">r/dataisbeautiful DataViz battle</a>).</p><p>Although I did not participate in the battle, I took a stab a making my own visualization. Whether it’s “beautiful” or not is still up for debate. My goal was to make a compact, and informative, graphic conveying as much relevant information as possible. Here is what I came up with. Code used to generate this figure is at the bottom of this post.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NbkPbAV3NCPNtDbfNcds7w.png" /><figcaption>Summary diagram: Left shows the duration of ice cover in days for each year. Darker circles indicate years where the lake thawed and refroze during the season. Black line indicates the linear trend. Top right shows the ice-off date and bottom right shows the ice-on date (image by author)</figcaption></figure><p>You will notice a distinct downward trend in the duration of ice cover, due to the ice-on date occurring later in the season and the ice-off date occurring earlier. This trend is mainly the result of human-caused warming, which has a more dramatic impact on the winter months. See the blog post below for a more detailed explanation of this.</p><p><a href="https://medium.com/climate-conscious/climate-change-in-madison-wi-f9800d6461ab">Climate Change in Madison, WI</a></p><p>And for another wintery climate related story, check out this post discussing snowfall trends in Minnesota.</p><p><a href="https://medium.com/age-of-awareness/does-record-snowfall-disprove-global-warming-25fd90a78c71">Does record snowfall disprove global warming?</a></p><h4>Code used to generate the summary diagram</h4><p>The code below was used to generate the summary diagram above. I am providing it here in the hope that it serves as a useful reference to help you enhance your plotting skills and create your own insightful visualizations.</p><pre>from collections import defaultdict as defaultdict<br>from datetime import datetime<br><br>import pandas as pd<br>import numpy as np<br>from sklearn import linear_model as linear_model<br>import matplotlib as matplotlib<br>import matplotlib.pyplot as plt<br>from matplotlib.ticker import AutoMinorLocator<br><br>import mendotapy<br><br><br>#========================<br># load the data<br>#========================<br>df = mendotapy.load()<br><br>#========================<br># create figure and axes<br>#========================<br>fig = plt.figure(figsize=(12, 6))<br>gs = matplotlib.gridspec.GridSpec(nrows=2, ncols=2,  width_ratios=[1, 1])<br>ax0 = fig.add_subplot(gs[:, 0])<br>ax1 = fig.add_subplot(gs[0, 1])<br>ax2 = fig.add_subplot(gs[1, 1])<br><br>#========================<br># duration plot<br>#========================<br>ax = ax0<br><br>x = df.utils.season_start() <br>y = df[&#39;duration&#39;]<br><br>ax.scatter(x, y,<br>           marker=&#39;o&#39;,<br>           s=100,<br>           linewidth=3,<br>           color=(1, 1, 1, 1),<br>           edgecolor=[0.7, 0.7, 0.7],<br>           clip_on=False,<br>           zorder=2,<br>           )<br><br># darker edgecolor if thawed during season<br>duplicates = df[&#39;season&#39;].value_counts()[df[&#39;season&#39;].value_counts() &gt; 1].index<br>filt = df[&#39;season&#39;].isin(duplicates)<br>x_thaw = df[filt].utils.season_start() <br>y_thaw = df.loc[filt, &#39;duration&#39;]<br><br>ax.scatter(x_thaw, y_thaw,<br>           marker=&#39;o&#39;,<br>           s=100,<br>           linewidth=3,<br>           color=(1, 1, 1, 1),<br>           edgecolor=[0.3, 0.3, 0.3],<br>           clip_on=False,<br>           zorder=2,<br>           )<br><br><br># Hide the right and top spines<br>ax.spines[&#39;right&#39;].set_visible(False)<br>ax.spines[&#39;left&#39;].set_visible(False)<br>ax.spines[&#39;top&#39;].set_visible(False)<br>ax.spines[&#39;bottom&#39;].set_visible(False)<br><br># set axis limits<br>ax.set_ylim([0, 170])<br>ax.set_xlim([1855, 2022])<br><br># tick marks<br>ax.set_xticks(np.arange(1860, 2021, 40))<br>ax.set_yticks(np.arange(14, 180, 14))<br><br># major/minor tick lines<br>ax.grid(axis=&#39;y&#39;, which=&#39;major&#39;, color=[0.8, 0.8, 0.8], linestyle=&#39;-&#39;)<br><br># make labels<br>ax.tick_params(axis=&#39;x&#39;, labelsize=16)<br>ax.tick_params(axis=&#39;y&#39;, labelsize=16)<br><br># Turn off the display of all ticks.<br>ax.tick_params(axis=&#39;both&#39;,<br>               which=&#39;major&#39;,  # Options for both major and minor ticks<br>               top=&#39;off&#39;,  # turn off top ticks<br>               left=&#39;off&#39;,  # turn off left ticks<br>               right=&#39;off&#39;,  # turn off right ticks<br>               bottom=&#39;off&#39;,  # turn off bottom ticks<br>               length=0,<br>               )<br><br># annotations<br>title_str = &quot;&quot;&quot;Duration of Ice Cover (Days)&quot;&quot;&quot;<br>ax.set_title(title_str, fontsize=18, fontweight=&#39;bold&#39;, ha=&#39;center&#39;)<br><br>x_new = []<br>y_new = []<br>for xi, yi in zip(x,y):<br>    if yi is not None:<br>        x_new.append(xi)<br>        y_new.append(yi)<br><br># linear regression<br>regr_out = linear_model.LinearRegression()<br>regr_out.fit(np.array(x_new).reshape(-1,1), np.array(y_new).reshape(-1,1))<br><br># plot trend line<br>ax.plot(x_new, regr_out.predict(np.array(x_new).reshape(-1,1)), color=&#39;k&#39;, linewidth=3)<br><br><br># Annotate the plot with the equation of the trend line<br>trend_per_decade = f&quot;trend: {(regr_out.coef_).item() * 10:.0f} days / decade&quot;<br>plt.annotate(trend_per_decade, xy=(-0.9, 2.05), xycoords=&#39;axes fraction&#39;, fontsize=12, color=&#39;k&#39;)<br>    <br>#========================<br># iceoff plot<br>#========================<br><br>ax = ax1<br><br>ax.plot(df.utils.season_start(),<br>        [result if result is None else result + 365 for result in df.utils.iceoff_doy_wrapped()], color=[0.5, 0.5, 0.5], linewidth=2)<br><br># Range ov axes<br>ax.set_ylim([340, 492])<br>ax.set_xlim([1855, 2022])<br><br>yticks_start_end_step = (340+7, 500, 14)<br>xticks_start_end_step = (1860, 2021, 40)<br><br># Turn off the display of all ticks.<br>ax.tick_params(which=&#39;both&#39;,  # Options for both major and minor ticks<br>               top=&#39;off&#39;,  # turn off top ticks<br>               left=&#39;off&#39;,  # turn off left ticks<br>               right=&#39;off&#39;,  # turn off right ticks<br>               bottom=&#39;off&#39;)  # turn off bottom ticks<br><br># Remove x tick marks<br>plt.setp(ax.get_xticklabels(), rotation=0)<br><br># Hide the right and top spines<br>ax.spines[&#39;right&#39;].set_visible(False)<br>ax.spines[&#39;left&#39;].set_visible(False)<br>ax.spines[&#39;top&#39;].set_visible(False)<br>ax.spines[&#39;bottom&#39;].set_visible(False)<br><br># major/minor tick lines<br>ax.grid(axis=&#39;y&#39;, which=&#39;major&#39;, color=[0.8, 0.8, 0.8], linestyle=&#39;-&#39;)<br><br># Turn off the display of all ticks.<br>ax.tick_params(axis=&#39;both&#39;,<br>               which=&#39;major&#39;,  # Options for both major and minor ticks<br>               top=&#39;off&#39;,  # turn off top ticks<br>               left=&#39;off&#39;,  # turn off left ticks<br>               right=&#39;off&#39;,  # turn off right ticks<br>               bottom=&#39;off&#39;,  # turn off bottom ticks<br>               length=0,<br>               )<br><br># Only show ticks on the left and bottom spines<br>ax.yaxis.set_ticks_position(&#39;left&#39;)<br>ax.xaxis.set_ticks_position(&#39;bottom&#39;)<br><br># Don&#39;t allow the axis to be on top of your data<br>ax.set_axisbelow(True)<br>ax.set_xticks(np.arange(*xticks_start_end_step))<br>ax.set_yticks(np.arange(*yticks_start_end_step))<br><br>ax.set_yticklabels([datetime.fromordinal(doy).strftime(&#39;%b-%d&#39;)<br>                    for doy in range(*yticks_start_end_step)])<br><br># Labels<br>ax.tick_params(axis=&#39;x&#39;, labelsize=16)<br>ax.tick_params(axis=&#39;y&#39;, labelsize=16, labelleft=False, labelright=True)<br><br>ax.xaxis.set_ticklabels([])<br><br># Annotations<br>title_str = &quot;&quot;&quot;Ice-off date&quot;&quot;&quot;<br>ax.set_title(title_str, fontsize=18, fontweight=&#39;bold&#39;, ha=&#39;center&#39;)<br><br><br>#========================<br># iceon plot<br>#========================<br>ax = ax2<br>ax.plot(df.utils.season_start(),<br>        df.utils.iceon_doy_wrapped(), color=&#39;k&#39;, linewidth=2)<br><br># Range ov axes<br>ax.set_ylim([320, 440])<br>ax.set_xlim([1855, 2022])<br><br>yticks_start_end_step = (320+7, 440, 14)<br>xticks_start_end_step = (1860, 2021, 40)<br><br># Turn off the display of all ticks.<br>ax.tick_params(which=&#39;both&#39;,  # Options for both major and minor ticks<br>               top=&#39;off&#39;,  # turn off top ticks<br>               left=&#39;off&#39;,  # turn off left ticks<br>               right=&#39;off&#39;,  # turn off right ticks<br>               bottom=&#39;off&#39;)  # turn off bottom ticks<br><br># Remove x tick marks<br>plt.setp(ax.get_xticklabels(), rotation=0)<br><br># Hide the right and top spines<br>ax.spines[&#39;right&#39;].set_visible(False)<br>ax.spines[&#39;left&#39;].set_visible(False)<br>ax.spines[&#39;top&#39;].set_visible(False)<br>ax.spines[&#39;bottom&#39;].set_visible(False)<br><br># major/minor tick lines<br>ax.grid(axis=&#39;y&#39;, which=&#39;major&#39;, color=[0.8, 0.8, 0.8], linestyle=&#39;-&#39;)<br><br># Turn off the display of all ticks.<br>ax.tick_params(axis=&#39;both&#39;,<br>               which=&#39;major&#39;,  # Options for both major and minor ticks<br>               top=&#39;off&#39;,      # turn off top ticks<br>               left=&#39;off&#39;,     # turn off left ticks<br>               right=&#39;off&#39;,    # turn off right ticks<br>               bottom=&#39;off&#39;,   # turn off bottom ticks<br>               length=0,<br>               )<br><br># Only show ticks on the left and bottom spines<br>ax.yaxis.set_ticks_position(&#39;left&#39;)<br>ax.xaxis.set_ticks_position(&#39;bottom&#39;)<br><br># Don&#39;t allow the axis to be on top of your data<br>ax.set_axisbelow(True)<br><br>ax.set_xticks(np.arange(*xticks_start_end_step))<br>ax.set_yticks(np.arange(*yticks_start_end_step))<br><br>ax.set_yticklabels([datetime.fromordinal(doy).strftime(&#39;%b-%d&#39;)<br>                    for doy in range(*yticks_start_end_step)])<br><br># Labels<br>ax.tick_params(axis=&#39;x&#39;, labelsize=16)<br>ax.tick_params(axis=&#39;y&#39;, labelsize=16, labelleft=False, labelright=True)<br><br># Annotations<br>title_str = &quot;&quot;&quot;Ice-on date&quot;&quot;&quot;<br>ax.set_title(title_str, fontsize=18, fontweight=&#39;bold&#39;, ha=&#39;center&#39;)<br><br># save figure<br>fig.savefig(&quot;./lake-mendota.png&quot;, bbox_inches=&#39;tight&#39;, dpi=300)</pre><h4>Challenges making the plot</h4><p>The trickiest part about making this figure was displaying the date as tick marks. I am sure there is a more elegant solution, but my approach was to format ordinal dates:</p><pre>yticks_start_end_step = (340+7, 500, 14)<br>yticks = range(*yticks_start_end_step)</pre><pre>list_of_dates = [datetime.fromordinal(doy).strftime(&#39;%b-%d&#39;) for doy in yticks]</pre><pre>ax.set_yticklabels(list_of_dates)</pre><pre>datetime(year, month, day).timetuple().tm_yday</pre><p>The datetime.fromordinal() function uses the <a href="https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar">proleptic Gregorian ordinal</a>, where January 1 of year 1 has ordinal 1. I then format these as a date. Although I may be off by a day in leap years, I think this approach should suffice since it is for visualization purposes only.</p><h3>Conclusions</h3><p>Creating the mendotapy package was a fun and educational experience, allowing me to deepen my understanding of the datetimepackage and enhance my plotting skills. This post only scratches the surface of what can be done with this rich dataset. Now that mendotapy makes it easy to retrieve and work with the data, I encourage you to dive deeper, use it as a toy dataset for statistical analyses, and share your insights. Contributions and feedback are always welcome on GitHub, and I look forward to seeing how this data sparks your curiosity and innovation.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=03f2e95ac4bc" width="1" height="1" alt=""><hr><p><a href="https://medium.com/data-science/analyzing-lake-mendota-ice-phenology-withpython-03f2e95ac4bc">Analyzing Lake Mendota Ice Phenology with Python</a> was originally published in <a href="https://medium.com/data-science">TDS Archive</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Easy File Sharing with transfer.sh]]></title>
            <link>https://lukegloege.medium.com/easy-file-sharing-with-transfer-sh-ccf170e81eb9?source=rss-780866762df------2</link>
            <guid isPermaLink="false">https://medium.com/p/ccf170e81eb9</guid>
            <category><![CDATA[productivity]]></category>
            <category><![CDATA[file-sharing]]></category>
            <category><![CDATA[command-line]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[Luke Gloege, Ph.D.]]></dc:creator>
            <pubDate>Tue, 28 Jun 2022 22:30:38 GMT</pubDate>
            <atom:updated>2022-06-28T22:30:38.329Z</atom:updated>
            <content:encoded><![CDATA[<h4>Quick Post</h4><h4>Quickly send someone a small file download link with transfer</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*hS-AiyjfPP2Mx-C1" /><figcaption>Photo by <a href="https://unsplash.com/@suzanneemily?utm_source=medium&amp;utm_medium=referral">Suzanne Emily O’Connor</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>File sharing typically involves signing into a storage provider, uploading the file, and then remembering how to share it with someone else.</p><p>Although not a tedious process, this can be accomplished with a lot less brain power thanks to tools such as <a href="https://transfer.sh/"><strong>transfer.sh</strong></a><strong>.</strong></p><p>With a single terminal command, you can generate a temporary download link you can share with anybody. The link becomes inactive after two weeks.</p><p>This free service lets you upload unlimited data for short-term file sharing.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*awPhgN5RwUitntee-tiNYQ.png" /><figcaption>Screenshot used with permission (source: <a href="https://transfer.sh/">https://transfer.sh/</a>)</figcaption></figure><p>You can use this service right now via <a href="https://curl.se/">Client URL</a> (cURL). The code below will generate a shareable download link.</p><pre>curl --upload-file ./hello.txt https://transfer.sh/hello.txt</pre><h3>Transfer Function: Oh-My-Zsh</h3><p>If you use <a href="https://ohmyz.sh/">Oh-My-Zsh</a>, simply add the <a href="https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/transfer">transfer plugin</a> to your .zshrc.</p><pre>plugins=(... transfer)</pre><p>Now transferring a file or directory is as simple as:</p><pre>transfer filename<br>transfer directory</pre><h3>Transfer Function: Other shells</h3><p>You do not need Oh-My-Zsh in order to have a simple transfer.sh function.</p><p>The function below is an example from the “Sample use cases” on the <a href="https://transfer.sh/">transfer.sh webpage</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/944/1*bnCKu7e6ER_-ZCa0sZEIxw.png" /><figcaption>Screenshot used with permission. See the “Sample Use Cases” section of <a href="https://transfer.sh/">https://transfer.sh/</a> to copy the code</figcaption></figure><p>I have found this service handy to quickly transfer a file to colleagues or to a different computer I am working on — Sure, I could use scp, but sometimes I find a transfer command faster to type.</p><p>Be mindful of what you are transferring; I would not suggest using this service to send any sensitive information. However, you can encrypt your content first using <a href="https://medium.com/plain-and-simple/how-to-use-gpg-fc095e944120">GPG</a> or <a href="https://keybase.io/">keybase.io</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ccf170e81eb9" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Store Unlimited Data for Free on IPFS and Retrieve it with Python]]></title>
            <link>https://medium.com/data-science/store-unlimited-data-for-free-on-ipfs-and-retrieve-it-with-python-8db7297b493d?source=rss-780866762df------2</link>
            <guid isPermaLink="false">https://medium.com/p/8db7297b493d</guid>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[ipfs]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[web3]]></category>
            <dc:creator><![CDATA[Luke Gloege, Ph.D.]]></dc:creator>
            <pubDate>Mon, 27 Jun 2022 22:13:39 GMT</pubDate>
            <atom:updated>2022-06-28T02:11:18.931Z</atom:updated>
            <content:encoded><![CDATA[<h4>How to incorporate decentralized storage into your workflow</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/878/1*k-mdSPFAlLK-e18UqdAZKw.png" /><figcaption>“uploading” by Katerina Limpitsouni (source <a href="https://undraw.co/illustrations">https://undraw.co/illustrations</a>)</figcaption></figure><p>Emerging technologies, such as the InterPlanetary FileSystem (<a href="https://ipfs.io/">IPFS</a>), can contribute to an ecosystem that is more verifiable and open. Since IPFS relies on <a href="https://web3.storage/docs/concepts/content-addressing/">content identifiers</a> (CIDs) which are a hash of the content, you can be confident that the returned data is correct. In addition, IPFS is an open and public network. So anybody can access content on the network if they have the correct CID.</p><p>One project I am particularly excited about is <a href="https://web3.storage/">web3.storage</a>, which is a free service that reduces the friction of using decentralized storage.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JLfBUsMwbVYMp4gXwH_KUA.png" /><figcaption>Screenshot used with permission (source: <a href="https://web3.storage/pricing/">https://web3.storage/pricing/</a>)</figcaption></figure><p>In this post, I will …</p><ul><li>Provide an introduction to web3.storage and how to set it up</li><li>Provide examples of basic CRUD operations with IPFS in Python</li><li>Describe my current solution to create a decentralized data repository</li></ul><h3>Introduction to web3.storage</h3><blockquote>Our aim today is to provide a user-friendly experience that massively reduces the burden for onboarding new use cases into the web3 ecosystem today — while providing an upgrade path for the future. — <a href="https://web3.storage/">Web3.Storage</a></blockquote><p>Web3.Storage allows users and developers to use <a href="https://web3.storage/docs/concepts/decentralized-storage/">decentralized storage</a> provided by IPFS and the Filecoin network. Anything uploaded is duplicated across geographically distributed storage providers, ensuring the resiliency of the network. The service also handles the work of pinning your content across multiple servers.</p><p>Be mindful of what you upload, since anybody can access the content on the network. However, you can encrypt content before uploading. My rule of thumb is, <strong>only upload content you are comfortable with being permanently public.</strong></p><p>The permanent part is important. Unlike location-based addressing (e.g. URLs), <a href="https://web3.storage/docs/concepts/content-addressing/">content-based addressing</a> (e.g. CIDs) makes it challenging to remove data from the network once it has been uploaded since it can be pinned on multiple servers.</p><p>Your quota is initially capped at 1TiB but can be increased for free by submitting a request. The overhead cost is currently subsidized by Protocol Labs, this will likely switch to some form of crypto-native payment model in the near future (e.g. staking Filecoin to increase storage limits).</p><p>The backbone that holds everything together is IPFS, a hypermedia protocol designed to make the web more resilient by addressing data by its <em>content</em> instead of by its<em> location. </em>To do this, IPFS uses CIDs instead of URLs — which point to the server the data is hosted on.</p><p>There is a lot more to web3.storage and I encourage you to explore the <a href="https://web3.storage/docs/">docs</a> if you are also excited by this project — especially if you are a developer.</p><h4>Setting up web3.storage</h4><blockquote>Go to <a href="https://web3.storage/">https://web3.storage</a> to create an account</blockquote><p>See the <a href="https://web3.storage/docs/#create-an-account">documentation</a> for detailed instructions.</p><h4>Create API Token</h4><p>An <a href="https://web3.storage/docs/#get-an-api-token">API token</a> is necessary to use web3.storage from the command line</p><ol><li>Log into your <a href="https://web3.storage/account/">web3.storage account</a></li><li>Click on <strong>Account</strong> at the top and then <strong>Create API token</strong></li><li>Enter a descriptive name for your token and click <strong>Create</strong></li><li>You can click <strong>Copy</strong> to copy your new API token to your clipboard.</li></ol><p><em>Do not share your API key with anybody, it is specific to your account. You should also make a note of the Token field somewhere and securely store it.</em></p><h4>Install the w3 Command-Line Interface</h4><p>The <a href="https://github.com/web3-storage/web3.storage/tree/main/packages/w3#readme">w3 command-line interface</a> (CLI) is node-based tool to use web3.storage from the terminal</p><p>On a Mac, you can easily install node via <a href="https://brew.sh/">homebrew</a>, this is also install the node package manager (npm).</p><pre>brew install node</pre><p>Use npm to install the <a href="https://github.com/web3-storage/web3.storage/tree/main/packages/w3#readme">w3</a> command-line interface.</p><pre>npm install -g @web3-storage/w3</pre><p>Run the following command to connect w3 to web3.storage.</p><pre>w3 token</pre><p>You will be prompted for the API token you previously created.</p><p>The following displays information about each of the available commands.</p><pre>w3 --help</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nlfdWb_zuC_p2--xEbzO_Q.png" /><figcaption>Image by author (created using <a href="https://carbon.now.sh/">https://carbon.now.sh/</a>)</figcaption></figure><p><strong>Upload and Download Commands</strong></p><ul><li>w3 put /path/to/file(this is how we upload content to web3.storage)</li><li>w3 get CID(this is how we download content from a specific CID)</li></ul><p><strong>List your files on web3.storage</strong></p><pre>w3 list</pre><h4>Example using put</h4><p>First, create a text file with a message.</p><pre>echo &quot;Hello web3.storage&quot; &gt; hello.txt</pre><p>Let’s now use the put command to push the file to IPFS.</p><pre>w3 put hello.txt --name hello</pre><blockquote><em>hello</em> will be the name that appears in web3.storage, use w3 list to verify</blockquote><p>The CID and a <a href="https://web3.storage/docs/how-tos/retrieve/#using-an-ipfs-http-gateway">public gateway link</a> are output.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kEzeozShLVxBJEMC9JiI1g.png" /><figcaption>Image by author (created using <a href="https://carbon.now.sh/">https://carbon.now.sh/</a>)</figcaption></figure><p>If you followed the steps above exactly, then your CID should be identical to mine. The CID is a hash that uniquely identifies the content.</p><p>Use the link below to view the message through a public gateway.</p><pre><a href="https://dweb.link/ipfs/bafybeifzoby6p5khannw2wmdn3sbi3626zxatkzjhvqvp26bg7uo6gxxc4">https://dweb.link/ipfs/bafybeifzoby6p5khannw2wmdn3sbi3626zxatkzjhvqvp26bg7uo6gxxc4</a></pre><blockquote><strong>Note:</strong> the link will be different if your message is different</blockquote><h3>Retrieve Content with Python</h3><p>In the future, web3.storage will hopefully be S3 compliant, meaning we can access data stored there similar to how we access data in S3 buckets.</p><p>For now, we can use HTTP requests to read the data into Python. However, libraries like pandas allow us to directly read CSV files from a gateway URL. Also, ipfsspec allows us to read zarr data stores from IPFS with xarray.</p><p>I will demonstrate reading each of these in the following sections</p><h4>Reading JSON</h4><p>Here is an example of reading a .json file stored on IPFS.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/bc6c2d3b804ecf5c830f11e69e96a41c/href">https://medium.com/media/bc6c2d3b804ecf5c830f11e69e96a41c/href</a></iframe><h4>Reading CSV files</h4><p>If you have a CSV file, you can read it directly into a pandas DataFrame.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/faac2cc826f6ed33cb0b649523dc65c0/href">https://medium.com/media/faac2cc826f6ed33cb0b649523dc65c0/href</a></iframe><h3>Zarr and IPFS</h3><p>The <a href="http://zarr.readthedocs.io/en/stable/">Zarr</a> format is new storage format which makes large datasets easily accessible to distributed computing, making it an improvement over the commonly used NetCDF — a format for storing multidimensional data.</p><p>If you starting to switch to zarr format, I encourage you check out Pangeo’s “<a href="https://pangeo.io/data.html#guide-to-preparing-cloud-optimized-data">Guide to preparing cloud-optimized data</a>”.</p><p>NetCDF files are still very common and <a href="http://xarray.pydata.org/en/latest/io.html#zarr">xarray’s functions</a> make it easy to convert these NetCDF files to zarr data stores.</p><pre>import xarray as xr<br>ds = xr.open_dataset(&#39;/path/to/file.nc&#39;)<br>ds.to_zarr(&quot;./file.nc&quot;, consolidated=True)</pre><p>consolidated=True creates a “hidden” dotfile along with the data store that makes reading from zarr faster with xarray. In a simple test, I found reading consolidated data stores to be 5 times faster than unconsolidated.</p><p><strong>zarr (consolidated): </strong>635 ms</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3cc3965ecd3bf024c0e3e8b26993bd68/href">https://medium.com/media/3cc3965ecd3bf024c0e3e8b26993bd68/href</a></iframe><p><strong>zarr (unconsolidated): </strong>3.19 s</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9cd6540acd85a688fc124bb7078eb51c/href">https://medium.com/media/9cd6540acd85a688fc124bb7078eb51c/href</a></iframe><p>If you would like to test out the code above, I uploaded <a href="https://psl.noaa.gov/data/gridded/data.noaa.oisst.v2.html">NOAA Optimum Interpolation SST V2 dataset</a> in consolidated and unconsoldiated zarr format to IPFS. This dataset provides weekly means of ocean sea surface temperature (SST) from 1990 to the present with a 1 degree spatial resolution.</p><p>The gateway URL for this data is shown below</p><pre><a href="https://dweb.link/ipfs/bafybeicocwmfbct57lt62klus2adkoq5rlpb6dfpjt7en66vo6s2lf3qmq">https://dweb.link/ipfs/bafybeicocwmfbct57lt62klus2adkoq5rlpb6dfpjt7en66vo6s2lf3qmq</a></pre><h4>Upload Zarr data stores to IPFS</h4><p>When uploading zarr files to IPFS you have to make sure to upload the “hidden” dotfiles. With w3 this entails adding the --hidden flag:</p><pre>w3 put ./* --hidden --name filename.zarr</pre><h4>Read Zarr data stores from IPFS</h4><p>In order to read zarr data stores from IPFS with xarray you will need the <a href="https://github.com/fsspec/ipfsspec">ipfsspec</a> package (as well as xarray and zarr)</p><pre>conda install -c conda-forge xarray zarr<br>pip install ipfsspec</pre><p>ipfsspec ensures xarray can interpret the IPFS protocol.</p><p>Notice in the example below I am using the IPFS protocol instead of the gateway URL with HTTPS. However, behind the scenes the code is actually reading from a gateway.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/59f4eebc42b74c580aa6ef8fda652262/href">https://medium.com/media/59f4eebc42b74c580aa6ef8fda652262/href</a></iframe><h3>Decentralized Data Repository</h3><p>IPFS can be used as a data repository. However, you need to link the CID to something that is human-readable in order for this to be a viable solution. I am not aware of any best practices surrounding CID management. However, taking some cues from <a href="https://docs.ipfs.io/how-to/best-practices-for-nft-data/">best practices for NFT data</a>, my current approach is to store CIDs and their associated file name as a key:value pair in JSON and then use a <a href="https://www.mongodb.com/nosql-explained">NoSQL database</a>, such as <a href="https://www.mongodb.com/">MongoDB</a>, for queries.</p><p>After you upload the content to web3.storage you add a new record to the file that identifies what the dataset is and the CID of the content. This is the minimum amount of information needed.</p><p>Here is an example of CIDs stored as a JSON array.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/64620e09c79e6bd99a657031cb1590df/href">https://medium.com/media/64620e09c79e6bd99a657031cb1590df/href</a></iframe><p>The <a href="https://www.mongodb.com/docs/drivers/pymongo/">pymongo</a> package makes it easy to work with MongoDB in Python.</p><ul><li><a href="https://medium.com/analytics-vidhya/connecting-to-mongodb-atlas-with-python-pymongo-5b25dab3ac53">Connecting to MongoDB Atlas with Python-PyMongo</a></li><li><a href="https://realpython.com/introduction-to-mongodb-and-python/">Introduction to MongoDB and Python in Real Python</a></li><li><a href="https://www.mongodb.com/docs/drivers/pymongo/">pymongo documentation</a></li></ul><p>Here is an example of inserting records into a collection and then querying the database for a specific CID.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/bd6c9fe11ccd79d01dcc9373bb388bf2/href">https://medium.com/media/bd6c9fe11ccd79d01dcc9373bb388bf2/href</a></iframe><h4>Alternative approach</h4><p>An alternative approach I have been considering, but have not implemented yet, is taking advantage of the w3 list output.</p><p>This displays the CID and name you supplied when you uploaded the content.</p><p>The idea is to write an awkscript to generate a JSON file from the output.</p><p>Some downsides or pitfalls to this include:</p><ul><li>Handling CIDs that point to directories instead of pure files</li><li>Ignoring CIDs that are irrelevant to your database</li></ul><p>The biggest hurdle I can see is dealing with directories. For that reason alone I am sticking with manually updating my database, especially since it is small and easy to manage — for now at least.</p><h3>Final thoughts</h3><p>IPFS could be a place for long-term storage with CIDs functioning like built-in version control.</p><p>The w3 CLI makes it easy to push data up to IPFS and any common format (JSON, CSV, Zarr) can be read from IPFS into Python.</p><p>However, one the challenges with using IPFS is CID management. My current solution to create a decentralized data repository is to store CIDs and their associated file names as key-value pairs in MongoDB. The solution is a little clunky and I am open to suggestions on how to improve this setup.</p><h4>Using IPFS for web hosting</h4><p>Another great application of IPFS is hosting a website. My personal webpage is hosted on IPFS. I think I came up with an elegant solution to manage a decentralized website, you can read about my solution in the article below.</p><p><a href="https://betterprogramming.pub/modern-way-to-host-a-site-on-ipfs-7941b65d27c3">Modern Way to Host a Site on Ipfs</a></p><h4>Storj: another decentralized storage option</h4><p>Finally, IPFS is not your only option. If you want an S3 compatible object storage, then I encourage you to checkout Storj. The Storj solution relies on storing shards of encrypted data across a decentralized network as opposed to duplicating your data. This is a paid service but is very affordable, especially if you are currently using AWS. I personally use Storj for my day-to-day work. Check out the article below if you are interested in learning how I use Storj.</p><p><a href="https://betterprogramming.pub/storj-decentralized-cloud-storage-my-new-favorite-cloud-object-storage-99d95ddc4e6a">Storj Decentralized Cloud Storage: My New Favorite Cloud Object Storage</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8db7297b493d" width="1" height="1" alt=""><hr><p><a href="https://medium.com/data-science/store-unlimited-data-for-free-on-ipfs-and-retrieve-it-with-python-8db7297b493d">Store Unlimited Data for Free on IPFS and Retrieve it with Python</a> was originally published in <a href="https://medium.com/data-science">TDS Archive</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Modern Way to Host a Site on IPFS]]></title>
            <link>https://medium.com/better-programming/modern-way-to-host-a-site-on-ipfs-7941b65d27c3?source=rss-780866762df------2</link>
            <guid isPermaLink="false">https://medium.com/p/7941b65d27c3</guid>
            <category><![CDATA[web3]]></category>
            <category><![CDATA[ipfs]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[Luke Gloege, Ph.D.]]></dc:creator>
            <pubDate>Fri, 24 Jun 2022 15:48:00 GMT</pubDate>
            <atom:updated>2022-06-25T10:15:57.185Z</atom:updated>
            <content:encoded><![CDATA[<h4>Using web3.storage, GitHub Actions, and IPNS</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gvSowVP7_JbYo5BM3FFCdQ.png" /><figcaption>“Profile” by Katerina Limpitsouni (source <a href="https://undraw.co/illustrations">https://undraw.co/illustrations</a>)</figcaption></figure><p>This article is an updated version of my “Guide to Hosting Websites on IPFS.”</p><p><a href="https://betterprogramming.pub/a-guide-to-hosting-websites-on-ipfs-d2efad40ed3">A Guide to Hosting Websites on IPFS</a></p><p>In the time since that article was published, I have streamlined my approach and distilled the process of updating my website down to three commands:</p><ol><li>git push</li><li>gh run view -v</li><li>w3 name publish &lt;KEY_ID&gt; &lt;CID&gt;</li></ol><p>The magic of this setup comes from:</p><ol><li>Using a GitHub action to push the site to <a href="https://ipfs.io/">IPFS</a> via <a href="https://web3.storage/">web3.storage</a></li><li>Using <a href="https://docs.ipfs.io/concepts/ipns/">IPNS</a> to avoid manually changing the <a href="https://docs.ipfs.io/concepts/dnslink/#publish-content-path">DNS link</a>.</li></ol><p>There are a lot of steps to set up this workflow, but it is worth the effort to be able to update your decentralized site with only a few terminal commands.</p><h3>Setup web3.storage</h3><p>To make pushing content to IPFS easier, we are going to use <a href="https://web3.storage/">web3.storage</a>, which was created by the same team behind IPFS and <a href="https://filecoin.io/">Filecoin</a>.</p><p>Web3.storage allows you to upload an unlimited amount of data to IPFS and it takes care of <a href="https://docs.ipfs.io/concepts/persistence/#pinning-in-context">pinning</a> the data to multiple servers for you, which is awesome!</p><p>Go to <a href="https://web3.storage/">https://web3.storage</a> to create an account:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JLfBUsMwbVYMp4gXwH_KUA.png" /><figcaption>Screenshot by author (source: <a href="https://web3.storage/pricing/">https://web3.storage/pricing/</a>)</figcaption></figure><h3>Create web3.storage API Token</h3><p>In order to push content to web3.storage from the command line and from GitHub Actions, we are going to need an <a href="https://web3.storage/docs/#get-an-api-token">API token</a>.</p><ol><li>Log into your <a href="https://web3.storage/account/">web3.storage account</a></li><li>Click on Account at the top and then Create API token</li><li>Enter a descriptive name for your token and click Create</li><li>You can click Copy to copy your new API token to your clipboard.</li></ol><p><em>Do not share your API key with anybody, it is specific to your account. You should also make a note of the Token field somewhere and securely store it.</em></p><h3>Install the w3 Command-Line Interface</h3><p>The <a href="https://github.com/web3-storage/web3.storage/tree/main/packages/w3#readme">w3 command-line interface</a> (CLI) allows us to interact with web3.storage from the terminal. This is a node-based CLI, so you will first have to install node on your machine</p><p>If you are using a Mac, you can easily install node via <a href="https://brew.sh/">homebrew</a></p><pre>brew install node</pre><p>This will include the node package manager (npm), which we will use to install the <a href="https://github.com/web3-storage/web3.storage/tree/main/packages/w3#readme">w3</a> CLI.</p><pre>npm install -g @web3-storage/w3</pre><p>Now, let’s test to make sure w3 installed with the following command:</p><pre>w3 --help</pre><p>This will display all the commands available.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nlfdWb_zuC_p2--xEbzO_Q.png" /><figcaption>Image by author (created using <a href="https://carbon.now.sh/">https://carbon.now.sh/</a>)</figcaption></figure><p>Now that we know the CLI is installed, run the token command to connect the CLI to web3.storage.</p><pre>w3 token</pre><p>You will then be prompted to paste your API token.</p><p>This is the web3.storage API token created in the previous section.</p><p>⁂ API token saved signifies the CLI was successfully linked</p><h3>Put Files on web3.storage</h3><p>If you look back at the command available in w3 you will see one called put.</p><p>As you probably guessed, this is how we “put” content on IPFS.</p><pre>w3 put &lt;path&gt;</pre><p>As a simple example, create a text file with a message as follows:</p><pre>echo &quot;Hello web3.storage&quot; &gt; hello.txt</pre><p>Let’s now use the put command to push the file to IPFS:</p><pre>w3 put hello.txt --name hello</pre><blockquote>--name hello tells the command to name the file hello in web3.storage.</blockquote><p>The <a href="https://web3.storage/docs/concepts/content-addressing/">content identifier</a> (CID), as well as a public gateway link, are output.</p><pre><a href="https://dweb.link/ipfs/bafybeifzoby6p5khannw2wmdn3sbi3626zxatkzjhvqvp26bg7uo6gxxc4">https://dweb.link/ipfs/bafybeifzoby6p5khannw2wmdn3sbi3626zxatkzjhvqvp26bg7uo6gxxc4</a></pre><p>If you followed the steps above exactly, then your CID should be identical to mine. Remember, the CID is a hash that uniquely identifies the content.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kEzeozShLVxBJEMC9JiI1g.png" /><figcaption>Image by author (created using <a href="https://carbon.now.sh/">https://carbon.now.sh/</a>)</figcaption></figure><p>We can upload folders in the exact same way. Simply provide the put command with the path to the directory you want to upload.</p><p>Later in this post, we will use a GitHub action to automate this put step.</p><h3>Create an IPNS address</h3><p>CIDs are immutable and can not be changed. The InterPlanetary Name System (IPNS) solves this issue with mutable names that are a <a href="http://docs.ipfs.io.ipns.localhost:48084/concepts/hashing/">hash</a> of a public key. You can think of an IPNS address as a pointer to a CID.</p><p>You can create an IPNS address using w3 using the following command:</p><pre>w3 name create</pre><p>Your address will be displayed, but you can always use w3 name list to show the name. However, I would still write down the address and store it somewhere.</p><p>To link a CID to the IPNS address we use the publish command, as shown below:</p><pre>w3 name publish &lt;key_id&gt; &lt;cid&gt;</pre><blockquote>&lt;key_id&gt; is your IPNS address (what create name list displays) and &lt;cid&gt; is what we are linking to the address</blockquote><p>For now, don’t worry about linking any CID to your IPNS address. We will do that later.</p><h3>Setup GitHub Repo / GitHub Actions</h3><p>Create a GitHub repo to store your website code. If this is a person&#39;s webpage, I suggest naming the repo &lt;account_name&gt;.github.io, which will allow you to use <a href="https://pages.github.com/">GitHub Pages</a> too.</p><p>With your repo setup, let’s add an <a href="https://github.com/web3-storage/add-to-web3">action</a> that pushes the repo to web3.storage each time we use git push</p><p>Place the following code snippet in .github/workflows/web3_storage.yaml in your repo:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DuIngTMzhJ8N4dRVPMDgkA.png" /><figcaption>GitHub action trigged by “git push” to send the entire repository to IPFS, code available <a href="https://github.com/lgloege/lgloege.github.io/blob/master/.github/workflows/web3_storage.yaml"><strong>here</strong></a> (created using <a href="https://carbon.now.sh/">https://carbon.now.sh/</a>)</figcaption></figure><p>The line ${{ secrets.WEB3_STORAGE_TOKEN }} reads your API token, this is how the action knows to push to your account.</p><p>Store API token in GitHub with the following steps:</p><ol><li>Log into your <a href="https://github.com/login">GitHub account</a></li><li>Go to Settings</li><li>Then Secrets</li><li>Click on Actions and then New Repository Secret</li><li>Your token must be namedWEB3_STORAGE_TOKEN</li><li>Copy the web3.storage API token we created earlier into the field</li></ol><p>Now each time you run git push the action will be triggered and your repo will be uploaded to IPFS. If you try this, you should have a green check ✅ signifying there were no errors.</p><p>Click on the check to view the details of the action. This will display the CID and gateway URL (that is what the last two lines in the action are doing).</p><h3>Using the GitHub CLI</h3><p>You can use the GitHub CLI to view the CID in the terminal. This requires installing another command-line tool and linking it to your GitHub account.</p><p>The GitHub CLI is not integral to the setup but makes it so you can view the output from your actions in the terminal.</p><p>On Mac, we can install the gh CLI with homebrew, as follows:</p><pre>brew install gh</pre><p><em>See the </em><a href="https://cli.github.com/manual/installation"><em>documentation</em></a><em> for installing on a different OS.</em></p><p>Go to <a href="https://github.com/settings/tokens">https://github.com/settings/tokens</a> to create an access token. You can find details steps in the <a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token">documentation</a>. Note that this token is different from the web3.storage token we created earlier. This one is so the GitHub CLI can communicate with your GitHub account.</p><p>Once you have an access token, run the following command:</p><pre>gh auth login</pre><p>You will be prompted to enter your GitHub Authentication token</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JEx5__BJ7ko6qzfum7RNCw.png" /><figcaption>Image by author (created using <a href="https://carbon.now.sh/">https://carbon.now.sh/</a>)</figcaption></figure><p>We can use the gh command to view information about the actions we run:</p><pre>gh run view -v</pre><p><em>You have to run this command from a directory with a git remote setup, or you will receive the error: “failed to determine base repo: no git remotes found”</em></p><p>The -v flag here enables verbose output. Otherwise, you will be greeted with a message saying “For more information about the job, try: gh run view …”</p><p>Select the workflow you want to view and the output will open in a pagination tool.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kj2wkt-R9uGRsf6yYlicbg.png" /><figcaption>Image by author (created using <a href="https://carbon.now.sh/">https://carbon.now.sh/</a>)</figcaption></figure><p>This displays the output from the GitHub action, specifically the CID.</p><p><em>You can open the repo with </em><em>gh repo view username/repo --web if you prefer to copy the CID from the webpage.</em></p><p>Now that we know our CID, let’s link to the IPNS address we created earlier.</p><h3>Link CID to IPNS</h3><p>To link the CID to the IPNS address, use the following command:</p><pre>w3 name publish &lt;key_id&gt; &lt;cid&gt;</pre><blockquote>Note that &lt;key_id&gt; is the IPNS address and &lt;cid&gt; is the CID from the last section</blockquote><p>The benefit of this is the IPNS address is mutable. What this means is after we update the website we can have the address point to the updated CID.</p><p>Finally, let’s set up a DNSLink with our IPNS address.</p><h3>Setup DNS link</h3><p>You have to have a custom domain purchased through a DNS provider. I used <a href="https://www.cloudflare.com/">Cloudflare</a>, but any provider should work.</p><p>The idea here is to have our custom domain retrieve the content from a gateway URL, but we have to tell it the IPNS address to retrieve.</p><h4><strong>DNS linking instructions using Cloudflare</strong></h4><ol><li>Log into Cloudflare</li><li>Click on your active website and then on DNS in the left-hand column.</li><li>In the DNS management section, add a TXT and CNAME record.</li><li>Set the TXT record name to _dnslink.yourwebsite.com (note: make sure to replace yourwebsite.com with your own domain)</li><li>Set the TXT record content to dnslink=/ipns/&lt;key_id&gt; (note: &lt;key_id&gt; is your IPNS address</li><li>Set the CNAME record to any IPFS gateway (e.g. gateway.ipfs.io )</li></ol><p>As an example, here is a screenshot of my TXT and CNAME records:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-551YjdDDeBMd6ykQShSJA.png" /><figcaption>Image by author</figcaption></figure><p><em>DNS linking only needs to be done once with a changeable IPNS address.</em></p><h3>What Is Happening Here</h3><p>What we just did is linked our domain name (which is hosted on a central server) to a gateway server. That gateway server (which is also a centralized server) fetches our requested content from the IPFS network (which is decentralized).</p><p>In this way, your site is pseudo-hosted on IPFS. The gateway server is centralized and your website will be inactive if that server goes down.</p><p>However, the content of your website is still on the IPFS network. This gateway server approach is one of the ways we can have our decentralized website communicate with an IPFS-ignorant browser. Gateways are a stop-gap solution to this problem.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/697/0*bVxU36V5ohgUf224.png" /><figcaption>Schematic of how an IPFS gateway works. Requests are routed through a centralized gateway server and then the content on IPFS is retrieved and sent back to the user. (image source: modified <a href="https://undraw.co/">UnDraw</a> image by author)</figcaption></figure><h3>Final Thoughts</h3><p>I think I have distilled the process down as much as I can. This approach removes the need to update CIDs in the DNS link and everything can be accomplished from the command line with only three commands</p><h4><strong>The three commands to update the website</strong></h4><ol><li>git push : this updates repo and pushes the website to IPFS</li><li>gh run view -h : this is to view our CID</li><li>w3 name publish &lt;key_id&gt; &lt;cid&gt; : this updates the IPNS link</li></ol><p>The approach takes some time to set up, but I think it is worth the effort.</p><p>I am open to suggestions if anybody has ways to improve this workflow.</p><h3>Acronyms</h3><ul><li>InterPlanetary FileSystem (IPFS)</li><li>InterPlanetary Naming System(IPNS)</li><li>Content IDentifier (CID)</li><li>Domain Name System (DNS)</li><li>Command-Line Interface (CLI)</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7941b65d27c3" width="1" height="1" alt=""><hr><p><a href="https://medium.com/better-programming/modern-way-to-host-a-site-on-ipfs-7941b65d27c3">Modern Way to Host a Site on IPFS</a> was originally published in <a href="https://betterprogramming.pub/">Better Programming</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Reference Supplementary Material in Overleaf]]></title>
            <link>https://lukegloege.medium.com/how-to-reference-supplementary-material-in-overleaf-ddc415511ad7?source=rss-780866762df------2</link>
            <guid isPermaLink="false">https://medium.com/p/ddc415511ad7</guid>
            <category><![CDATA[overleaf]]></category>
            <category><![CDATA[latex]]></category>
            <category><![CDATA[science-writing]]></category>
            <dc:creator><![CDATA[Luke Gloege, Ph.D.]]></dc:creator>
            <pubDate>Mon, 06 Jun 2022 23:41:46 GMT</pubDate>
            <atom:updated>2022-06-06T23:41:46.167Z</atom:updated>
            <content:encoded><![CDATA[<h4>Scientific Writing</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*vSwUBLqs7Y3PP51h" /><figcaption>Photo by <a href="https://unsplash.com/@eugeniia?utm_source=medium&amp;utm_medium=referral">Eugenia Ai</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><blockquote><strong>TL;DR:</strong> you need to include the suppelementary material’s output.aux file in the project, then you can reference figures as if they are in the main file</blockquote><p><a href="https://www.overleaf.com/">Overleaf</a> makes working collaboratively on a scientific manuscript a breeze and popular journals, such as AGU, even have an <a href="https://www.overleaf.com/gallery/tagged/agu-official#.WBukk-ErLus">official template</a>.</p><p>If you have used overleaf to write a manuscript, then you have probably struggled with referencing your supplementary material in your main text. It is non-trivial how to do this in overleaf, which is why I am outlining the steps here.</p><h3>Steps to reference supplementary material</h3><ol><li>After you compile your supplementary material, click on the logs and output files between recompile and download PDF</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/448/1*RxGyiL44t39gAFCjmuX6xA.png" /></figure><p>2. After that, click on other logs and files in the bottom right</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/580/1*3AjLZoA616oEehUWOCkoQw.png" /></figure><p>3. This will bring up a box with different files. Click on output.aux and save this file somewhere on your computer</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/424/1*RC_V0iLCbOBG7b3_7eB8vQ.png" /></figure><p>4. Rename output.aux to the same name as your supplementary file. For example, if your supplementary material is in supplementary.tex then name the aux file supplementary.aux</p><p>5. Copy this aux file into your overleaf project</p><p>6. Now you can start referencing supplementary figures in the main file.</p><p>Keep in mind that if you change the supplementary file, such as adding a new figure, then you will have to replace the aux file.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ddc415511ad7" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Python Package for Analysis Ready Lake Mendota Ice Phenology Data]]></title>
            <link>https://lukegloege.medium.com/python-package-for-analysis-ready-lake-mendota-ice-phenology-data-9c92becaaad3?source=rss-780866762df------2</link>
            <guid isPermaLink="false">https://medium.com/p/9c92becaaad3</guid>
            <category><![CDATA[lake]]></category>
            <category><![CDATA[madison-wi]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[climate-data-science]]></category>
            <category><![CDATA[climate-change]]></category>
            <dc:creator><![CDATA[Luke Gloege, Ph.D.]]></dc:creator>
            <pubDate>Tue, 22 Mar 2022 02:00:32 GMT</pubDate>
            <atom:updated>2022-03-22T02:01:32.321Z</atom:updated>
            <content:encoded><![CDATA[<h4>QUICK POST</h4><h4>How to start analyzing/visualizing a 150-year ice record</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/772/1*qzdryiQmJMqIaY99HMDsMQ@2x.png" /><figcaption>Lake Mendota ice phenology data. Solid black markers in the duration plot indicate years where the lake froze and thawed twice in a single season. (image by author)</figcaption></figure><p>In this quick pos I will present mendotapy, a Python package to read analysis-ready Lake Mendota ice phenology data into memory. This ice phenology dataset is maintained by the <a href="https://www.aos.wisc.edu/~sco/lakes/Mendota-ice.html">Wisconsin State Climatology office</a>.</p><h3>Get the data</h3><blockquote><a href="https://github.com/lgloege/mendotapy">mendotapy source code</a> is available on GitHub.</blockquote><p>Install the package via pip</p><pre>pip install mendotapy</pre><p>Import the package and load the data as an analysis-ready Pandas dataFrame</p><pre>import mendotapy<br>df = mendotapy.load()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dLTglIg3qA6Qrk79v-H38Q.png" /><figcaption>dataFrame containing analysis-read data (image by author)</figcaption></figure><h4>Data Columns</h4><ul><li><strong>season: </strong>the winter season</li><li><strong>iceon_date: </strong>first<strong> </strong>date majority of the lake surface is estimated to be frozen</li><li><strong>iceoff_date: </strong>latest date of ice breakup</li><li><strong>duration: </strong>number of days the lake remained frozen during the season</li><li><strong>n_freezes: </strong>number of freeze/breakup cycles during the season</li><li><strong>n_iceon_dates: </strong>list of ice-on dates during the season</li><li><strong>n_iceff_dates: </strong>list of ice-off dates during the season</li><li><strong>iceon_doy: </strong>day-of-year the lake first froze</li><li><strong>iceoff_doy: </strong>day-of-year the lake last broke up during the season</li></ul><h3>Final Thoughts</h3><p>This is a great dataset See the <a href="https://www.reddit.com/r/dataisbeautiful/comments/a2p5f0/battle_dataviz_battle_for_the_month_of_december/">r/dataisbeautiful DataViz battle</a> from 2018 for creative visualizations. to create the title figure for this post simply run the following:</p><pre>import mendotapy<br>mendotapy.plot()</pre><p>Check out the following story for a discussion on why ice cover is decreasing.</p><p><a href="https://medium.com/climate-conscious/climate-change-in-madison-wi-f9800d6461ab">Climate Change in Madison, WI</a></p><p><em>Thank you for reading and supporting Medium writers</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9c92becaaad3" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Xarray Recipes for Earth Scientists]]></title>
            <link>https://medium.com/data-science/xarray-recipes-for-earth-scientists-c12a10c6a293?source=rss-780866762df------2</link>
            <guid isPermaLink="false">https://medium.com/p/c12a10c6a293</guid>
            <category><![CDATA[geospatial]]></category>
            <category><![CDATA[earth-science]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[xarray]]></category>
            <category><![CDATA[climate-data-science]]></category>
            <dc:creator><![CDATA[Luke Gloege, Ph.D.]]></dc:creator>
            <pubDate>Sat, 26 Feb 2022 03:59:31 GMT</pubDate>
            <atom:updated>2022-02-26T04:50:20.738Z</atom:updated>
            <content:encoded><![CDATA[<h4>CLIMATE DATA SCIENCE</h4><h4>Code snippets to help you analyze your data</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ShM0muCDQwRdwtvw" /><figcaption>Photo by <a href="https://unsplash.com/@flyd2069?utm_source=medium&amp;utm_medium=referral">FLY:D</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Earth science data typically comes packaged in NetCDF files with labeled dimensions, making xarray the perfect analysis tool. Xarray has some powerful, yet versatile, built-in methods, such as resample(), groupby(), and concat(). This package is integral to geospatial analysis, which is why it is the backbone of the <a href="https://pangeo.io/architecture.html">Pangeo software stack</a>. Xarray is available under the open source <a href="http://www.apache.org/licenses/LICENSE-2.0.html">Apache License</a>.</p><p>This article is a collection of snippets geared towards Earth scientists.</p><pre><strong>Table of Contents</strong></pre><pre><a href="#b377">0. Installation</a><br>  <a href="#0b57">0.1 Tutorial dataset</a><br><a href="#e7e2">1. Climatology and Anomalies</a><br><a href="#ab3a">2. Downsampling: Monthly Average</a><br>  <a href="#b23c">2.1 Monthly Max, Min, Median, etc.</a><br>  <a href="#4043">2.2 N-month Average</a><br><a href="#3865">3. Upsampling: Daily Interpolation</a><br><a href="#c729">4. Weighted Average</a><br><a href="#544f">5. Moving Average</a><br><a href="#88e6">6. Ensemble Average</a><br><a href="#3b71">7. Assign New Variables or Coordinate</a><br>  <a href="#2360">7.1 Assign new variable</a><br>  <a href="#4417">7.1 Changing time coordinate</a><br>  <a href="#6c13">7.2 Changing longitude coordinate</a><br><a href="#7044">8. Select a Specific Location</a><br><a href="#3108">9. Fill in Missing Values</a><br>  <a href="#a0d9">9.1 Fill NaN with value</a><br>  <a href="#d007">9.2 Replace with climatology</a><br>  <a href="#9608">9.3 Interpolate between points</a><br>  <a href="#2521">9.4 Forward/backward filling</a><br><a href="#7a5f">10. Filter Data</a><br><a href="#bbe5">11. Mask Data</a><br><a href="#7d35">12. Final Thoughts</a><br>  <a href="#a90b">12.1 Split-apply-combine</a></pre><h3>0. Installation</h3><p>The <a href="https://xarray.pydata.org/en/v0.21.1/getting-started-guide/installing.html#">developers recommend</a> using the conda package manager and the community-maintained conda-forge channel for installation.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0915c3f9a8891b616d0427a909fd02d4/href">https://medium.com/media/0915c3f9a8891b616d0427a909fd02d4/href</a></iframe><h4>0.1 Tutorial dataset</h4><p>Once installed, try loading this <a href="https://xarray.pydata.org/en/stable/generated/xarray.tutorial.open_dataset.html">tutorial dataset</a>, which is two years of air temperature sampled 4 times daily.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d4814eeba145848073b071da4367ad3f/href">https://medium.com/media/d4814eeba145848073b071da4367ad3f/href</a></iframe><blockquote>This dataset can be used to explore the recipes in this article</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/971/1*OVpBbqW6AHswv4rUy9NPkg@2x.png" /><figcaption>image by author</figcaption></figure><h3>1. Climatology and Anomalies</h3><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b7117c38b691f079f80275925598d97c/href">https://medium.com/media/b7117c38b691f079f80275925598d97c/href</a></iframe><ul><li><strong>Climatology</strong>: A monthly climatology entails averaging all the Januaries in a time series, then all the Februaries, etc.</li><li><strong>Anomalies</strong>: These are deviations from climatology or the difference between the original time series and climatology. For example, if the time series is monthly, then the January 2013 anomaly is the January 2013 value minus the January climatology.</li></ul><p><a href="https://xarray.pydata.org/en/stable/generated/xarray.Dataset.groupby.html">groupby()</a>will collect all the like coordinate values, in this case, each group contains each month— regardless of the year. We then calculate the mean across each group and combines them back together</p><p>The argument supplied to groupby() specifes what we want to group. You can get the months via the <a href="https://xarray.pydata.org/en/stable/generated/xarray.core.accessor_dt.DatetimeAccessor.html">.dt accessor</a>, which is what we are doing in this example.</p><p>When calculating monthly anomalies, we first group by month and then subtract the climatology. For example, the January climatology is subtracted from each member in the January group, this is then repeated for every other month.</p><h3>2. Downsampling: Monthly Average</h3><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/948371e8f15fdbf204ffc64f46a5e425/href">https://medium.com/media/948371e8f15fdbf204ffc64f46a5e425/href</a></iframe><ul><li><strong>Downsampling</strong>: Decreasing the frequency of the samples. For example, going from a daily time series to monthly</li></ul><p>To achieve this with xarray we use <a href="https://xarray.pydata.org/en/stable/generated/xarray.Dataset.resample.html">.resample()</a>. The argument supplied specifies the temporal dimension (e.g. time) and resample frequency (e.g. monthly). In the example above, the sampling frequency string &#39;1MS’ means sample monthly with the new time vector centered on the start of the month (<em>2000-Jan.-01</em>, <em>2000-Feb.-01</em>, etc.).</p><p>The sampling frequency (e.g. ‘1MS’) specifies how to resample the data. See offset aliases in <a href="https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#offset-aliases">Pandas documentation</a> for other options.</p><p>To calculate the monthly average you have to attach .mean(). Removing this method will return a DatasetResample object. Always specify what you want to do with the samples.</p><h4><strong>2.1 Monthly Max, Min, Median, etc.</strong></h4><p>There is a suite of other methods you can use with resample() including: max ,min ,median,std,var,quantile,sum</p><p>Check out the <a href="https://xarray.pydata.org/en/stable/generated/xarray.Dataset.resample.html">resample documentation</a> for more details</p><h4>2.2 N-month Average</h4><p>Instead of averaging monthly, what if you wanted to average every 2 months? In this case, you would simply supply the number of months to sample by in the sampling frequency string:</p><ul><li><strong>Monthly average</strong>: ‘1MS’ (the 1 is optional)</li><li><strong>2-month average</strong>: ‘2MS’</li><li>…</li><li><strong>15-month average</strong>: ‘15MS’ (I don’t know why you would want to do this, but you can)</li></ul><h3>3. Upsampling: Daily Interpolation</h3><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/02b06a5e2ba52e0bb2938b6d631e4c0d/href">https://medium.com/media/02b06a5e2ba52e0bb2938b6d631e4c0d/href</a></iframe><ul><li><strong>Upsampling</strong>: Increasing the sampling frequency. For example, going from a monthly time series to daily</li></ul><p>Here is an example of upscaling data from a monthly resolution to daily using linear interpolation. In other words, converting a “low” temporal resolution (e.g. monthly) to a “high” resolution (e.g. daily). The argument supplied specifies the temporal dimension (e.g. time) and resample frequency (e.g. daily). In the example above the sampling frequency string &#39;1D’ means sample daily.</p><p>We then attach .interpolate(“linear”) to linearly interpolate between the points.</p><h3>4. Weighted Average</h3><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/34d84b095fdb5e5afb06244da488290b/href">https://medium.com/media/34d84b095fdb5e5afb06244da488290b/href</a></iframe><ul><li><strong>Weighted average</strong>: Average of values which are scaled by their importance. Calculated as the sum of weights multiplied by the values divided by the sum of the weights.</li></ul><p>When averaging geospatial spatial data, it is often important to weight the data so small regions do not skew the result. For instance, if your data is on a uniform latitude-longitude grid, then data near the poles occupy less area than the low latitudes. If you average temperature over the entire globe then you should weight the arctic less than the tropics since it occupies less area.</p><p>In this example, I am doing a common approach and weighting by the cosine of the latitude. This example is from the <a href="https://xarray.pydata.org/en/stable/examples/area_weighted_temperature.html">xarray webpage</a>.</p><p>When calculating a weighted average with xarray note that ds.weighted(weights) is an instance of the “weighted class.” In addition to a weighted mean, we can calculate a weighted standard deviation, weighted sum, etc. See options <a href="https://xarray.pydata.org/en/stable/generated/xarray.core.weighted.DatasetWeighted.html">here</a>.</p><p>Another approach is weight by grid cell area, see the post below</p><p><a href="https://towardsdatascience.com/the-correct-way-to-average-the-globe-92ceecd172b7">The correct way to average the globe</a></p><h3>5. Moving Average</h3><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/53bb32c0ef391dcaa99199b15ff5c02e/href">https://medium.com/media/53bb32c0ef391dcaa99199b15ff5c02e/href</a></iframe><ul><li><strong>Moving average: </strong>Technique to smooth data by averaging within specific intervals. The interval is defined by a specific number of data points, termed the <em>window length</em>.</li></ul><p>Rolling mean/running mean/moving average is a technique to smooth short-term fluctuations to enhance the signal-to-noise ratio. You use the <a href="https://xarray.pydata.org/en/stable/generated/xarray.Dataset.rolling.html">.rolling()</a> method and supply it the dimension to apply the mean over and the window length.</p><p>In the example above I am applying a 30 day running mean over the air-temperature tutorial dataset. Since data is sampled 4 times each day there are 120 samples every 30 days.</p><p>An optional argument you can supply to rolling() is center=True , this will set the labels at the center of the window instead of the beginning.</p><h3>6. Ensemble Average</h3><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a96a42ebacbc9065aee0ab893315e399/href">https://medium.com/media/a96a42ebacbc9065aee0ab893315e399/href</a></iframe><ul><li><strong>Ensemble Average</strong>: Averaging multiple estimates of the same quantify. For example, the collection of CMIP models is an example of an ensemble.</li></ul><p>If you have a dataset with multiple related variables you can concatenate them across a new dimension and then perform statistics.</p><p>I previously wrote about this topic earlier describing a different technique to achieve this with xarray. I now prefer the code above.</p><p><a href="https://towardsdatascience.com/pythonic-way-to-perform-statistics-across-multiple-variables-with-xarray-d0221c78e34a">Pythonic Way to Perform Statistics Across Multiple Variables with Xarray</a></p><h3>7. Assign New Variables or Coordinate</h3><p>Xarray provides three “assign” methods:</p><ul><li><a href="https://xarray.pydata.org/en/stable/generated/xarray.Dataset.assign.html">.assign()</a> → Assign new data variables to a Dataset</li><li><a href="https://xarray.pydata.org/en/stable/generated/xarray.Dataset.assign_coords.html">.assign_coords()</a> → Assign new coordinates to a Dataset</li><li><a href="https://xarray.pydata.org/en/stable/generated/xarray.Dataset.assign_attrs.html">.assign_attrs()</a> → Assign new attributes to a Dataset</li></ul><h4>7.1 Assign new variable</h4><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ce63bf3d4effdc869f3b8a232f58bf90/href">https://medium.com/media/ce63bf3d4effdc869f3b8a232f58bf90/href</a></iframe><p>To add new variables to a dataset you can use .assign(). Keep in mind there is not an ‘inplace’ option with xarray, you will always have to explicitly assign the object to a variable.</p><p>In this example, I am changing the units from degrees Celsius to Kelvin and assigning it to a variable named temp_k. Units are very important, remember the <a href="https://solarsystem.nasa.gov/missions/mars-climate-orbiter/in-depth/">Mars Climate Orbiter</a> incident?</p><p>The second line is optional and simply adds attributes to the new variable.</p><h4>7.1 Changing time coordinate</h4><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d9ecc802d361039126334adf29f6b354/href">https://medium.com/media/d9ecc802d361039126334adf29f6b354/href</a></iframe><p>Here is an example of changing the time coordinate. This is useful when you want the time coordinate to start on the 15th of the month instead of the 1st.</p><h4>7.2 Changing longitude coordinate</h4><p>There are two conventions for longitude</p><ul><li><strong>“360 convention”</strong>: 0 to 360, with 0 at the prime meridian and values increasing Eastward</li><li><strong>“180 convention”</strong>: -180W to 180E, centered on zero at the prime meridian</li></ul><blockquote>I made up the convention names. Please let me know if proper names exist</blockquote><p><strong>Change from 180 to 360</strong></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/380bf86e03467bffe909955836874fd6/href">https://medium.com/media/380bf86e03467bffe909955836874fd6/href</a></iframe><p><strong>Change from 360 to 180</strong></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9ddca900ab5f616e1aaed37d216ce880/href">https://medium.com/media/9ddca900ab5f616e1aaed37d216ce880/href</a></iframe><p>Alternatively, you could use list comprehension to make this more intuitive.</p><h3>8. Select a Specific Location</h3><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/75c8bf07489b039255ca9e4ac58cee82/href">https://medium.com/media/75c8bf07489b039255ca9e4ac58cee82/href</a></iframe><p>Use sel() to select a specific latitude/longitude location. By default, sel() looks for exact matches in the dataset, but supplying method=’nearest’tells xarray to find the closest match to your selection.</p><h3>9. Fill in Missing Values</h3><p>Often times your data will have missing values you need to fill.</p><p>Unfortunately, there is not a “one size fits all” solution to this problem</p><p><strong>Common approaches:</strong></p><ul><li>Fill missing values with some value</li><li>Fill missing values with climatology</li><li>Interpolate between points</li><li>Propagate values</li></ul><h4>9.1 Fill NaN with value</h4><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3f3d37b26ef28113fd3e82a77514cf1d/href">https://medium.com/media/3f3d37b26ef28113fd3e82a77514cf1d/href</a></iframe><p>Using the fillna() method is a straightforward way to replace missing values some value. In the example above I am using 0 as the fill value, but this could be anything, the choice depends on the situation.</p><h4>9.2 Replace with climatology</h4><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a5386ab219075ceb1a90fb49b9427660/href">https://medium.com/media/a5386ab219075ceb1a90fb49b9427660/href</a></iframe><p>To replace with climatological values you first have to group the data via grouby() and then use fillna() supplied with the climatological values.</p><h4>9.3 Interpolate between points</h4><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/cb34ed8beb373a764cf9fd85aa848742/href">https://medium.com/media/cb34ed8beb373a764cf9fd85aa848742/href</a></iframe><p>Another approach is to interpolate across missing values using <a href="https://xarray.pydata.org/en/stable/generated/xarray.Dataset.interpolate_na.html">interpolate_na()</a>. Here I am linear interpolating across time.</p><blockquote>See the <a href="https://xarray.pydata.org/en/stable/generated/xarray.Dataset.interpolate_na.html"><strong>interpolate_na() documentation</strong></a> for a complete list of interp methods.</blockquote><h4>9.4 Forward/backward filling</h4><pre>ds.ffill(&#39;time&#39;)<br>ds.bfill(&#39;time&#39;)</pre><ul><li><a href="https://xarray.pydata.org/en/stable/generated/xarray.Dataset.ffill.html">Forward fill</a> → ffill() propagates values forward</li><li><a href="https://xarray.pydata.org/en/stable/generated/xarray.Dataset.bfill.html">Backward fill</a> → bfill() propagates values backward</li></ul><p>These methods fill NaN values with the first non-NaN value, with ffill() filling forward and bfill() filling backward along the supplied dimension.</p><h3>10. Filter Data</h3><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/19bff8059eeb9da7d1c22d1a5ff17cfc/href">https://medium.com/media/19bff8059eeb9da7d1c22d1a5ff17cfc/href</a></iframe><p>Select data based on a conditional expression. Only the data where the expression evaluates to True will be retained, everything else will be NaN.</p><h3>11. Mask Data</h3><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/169af484ed22efe132b41c60db32f7e9/href">https://medium.com/media/169af484ed22efe132b41c60db32f7e9/href</a></iframe><p>Mask data based on a conditional expression. Mask values will be True if the supplied condition is True and False otherwise. This can be turned into a mask of 1s and 0s simply by multiplying by 1.</p><h3>12. Final Thoughts</h3><p>I hope this post helped you with your work and illustrated the power of xarray. Keep in mind this post merely provides one person’s perspective. There may be more efficient or intuitive solutions to some of these problems. For that reason, I strongly encourage feedback and comments</p><p>A common task I did not include in this post is regridding data. I encourage you to check out the awesome <a href="https://xesmf.readthedocs.io/en/latest/">xESMF</a> package.</p><p>Finally, the examples in this post can be adapted for other purposes. For example, calculating climatology is really an application of a <a href="https://xarray.pydata.org/en/stable/user-guide/groupby.html#groupby">split-apply-combine strategy</a>.</p><h4>12.1 Split-apply-combine</h4><ol><li><strong>Split</strong> the data into groups,</li><li><strong>Apply</strong> a function to each group</li><li><strong>Combine</strong> all the groups back together.</li></ol><p>When calculating climatologies the “apply” step is mean. However, this can be swapped out for: max ,min ,median,std,var,quantile, or sum . A custom method can even be supplied by using map() .</p><p><em>Thank you for reading and supporting Medium writers</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c12a10c6a293" width="1" height="1" alt=""><hr><p><a href="https://medium.com/data-science/xarray-recipes-for-earth-scientists-c12a10c6a293">Xarray Recipes for Earth Scientists</a> was originally published in <a href="https://medium.com/data-science">TDS Archive</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Storj Decentralized Cloud Storage: My New Favorite Cloud Object Storage]]></title>
            <link>https://medium.com/better-programming/storj-decentralized-cloud-storage-my-new-favorite-cloud-object-storage-99d95ddc4e6a?source=rss-780866762df------2</link>
            <guid isPermaLink="false">https://medium.com/p/99d95ddc4e6a</guid>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[web3]]></category>
            <category><![CDATA[blockchain]]></category>
            <category><![CDATA[python]]></category>
            <dc:creator><![CDATA[Luke Gloege, Ph.D.]]></dc:creator>
            <pubDate>Wed, 23 Feb 2022 17:27:52 GMT</pubDate>
            <atom:updated>2022-02-23T17:27:52.388Z</atom:updated>
            <content:encoded><![CDATA[<h4>Storj offers 150 GB of free storage and seamless Python integration</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HOV_z44G_3_7CgpfTokGgA.png" /><figcaption>Connected world (source: <a href="https://undraw.co/">unDraw</a>)</figcaption></figure><p><a href="https://www.storj.io/">Storj DCS</a> (formerly Tardigrade) has matured to a point where it is easy to use and is compatible with S3 tools (such as s3fs), which makes migrating from AWS painless.</p><p>In this post, I will briefly outline how <a href="https://www.storj.io/">Storj Decentralized Cloud Storage</a> (DCS) works, how to create buckets using the command line, and how to read from buckets into Python.</p><pre><strong>Table of Contents</strong><br><a href="#8f6d">1. Storj Decentralized Cloud Storage</a><br>  <a href="#d903">1.1 How it works</a><br>  <a href="#12e9">1.2 Free tier </a><br><a href="#0049">2. Creating an account</a><br><a href="#8bb2">3. The Uplink CLI</a><br>  <a href="#b615">3.1 Uplink commands</a><br><a href="#0372">4. Read Data into Python</a><br>  <a href="#3644">4.1 Create an Access Grant</a><br>  <a href="#b458">4.2 Read a JSON file</a><br>  <a href="#dd42">4.3 Read CSV file with Pandas</a><br>  <a href="#3dfc">4.4 Read Multidimensional Data with Xarray</a><br><a href="#043f">5. Conclusions</a></pre><h3>1. Storj Decentralized Cloud Storage</h3><p>Storj offers a decentralized alternative to cloud storage providers such as Amazon Web Services (AWS). Data uploaded to Storj is encrypted, divided into chunks, and dispersed over uncorrelated nodes around the world, i.e. storage providers with different geographic locations, internet service providers, power supplies, etc. The network nodes are independent and communicate via a peer-to-peer communication protocol.</p><p>Anybody can operate a node to help secure the network. Running a node means hosting encrypted partial segments of someone else’s data and getting compensated for it. Compensation comes in the form of the <a href="https://www.gemini.com/cryptopedia/storj-coin-decentralized-storage">Storj token</a>, an ERC-20 token on the Ethereum blockchain.</p><h4>1.1 How it works</h4><p>Files uploaded to Storj DCS are encrypted, split into pieces, and stored on storage devices all over the world. Each file is split into 80 or more pieces and distributed across the network. Since there is no central point of failure, file loss is statistically unlikely thanks to redundancy that’s built into the system. Even if pieces are lost due to storage failure or nodes leaving the network, the file can still be reconstructed since only 29 pieces are needed. This is because Storj uses <a href="https://www.youtube.com/watch?v=jgO09opx56o">Reed-Solomon encoding</a>, the same encoding that powers QR codes, CDs, and DVDs. So far, the network has never lost a file. If you want to learn more, <a href="https://www.gemini.com/cryptopedia/storj-crypto-decentralized-storage">Cryptopedia</a> has a nice overview of Storj, check out the webpage at <a href="https://www.storj.io/how-it-works">storj.io/how-it-works</a>, or check out the <a href="https://www.storj.io/whitepaper">whitepaper</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/443/1*vBZxAGLzkQZJeGF4dqjG1Q@2x.png" /><figcaption>image by author</figcaption></figure><h4>1.2 Free tier</h4><p>Storj DCS has one of the best free tiers I have seen — <strong>150 GB of free storage</strong>. On top of that, the paid storage plan is $4 per TB per month and more cost-effective than AWS S3. Both integrate well with Python but, depending on your needs, Storj DCS has more value.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/978/1*pz63V77cdsKwXFAFaEmh_g@2x.png" /><figcaption>image by author</figcaption></figure><h3>2. Creating an account</h3><p>Begin by creating an account with Storj if you do not have one already</p><p><a href="https://www.storj.io/">Storj | Fast, secure cloud storage at a fraction of the cost.</a></p><p>Upon creation, you will see a page that looks like this</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*EWS0F2e7pmv1O-s4I1uNkQ@2x.png" /><figcaption>image by author</figcaption></figure><p>I suggest clicking on <strong>Continue in CLI, </strong>which will help you install the command-line interface (CLI). You will first see your satellite and API keys. <strong>Make sure to save these as you only see them once. </strong>Then you will see command prompts to install the uplink CLI. The next few pages will walk you through setting up the uplink CLI to interact with the Storj network and it provides an example of how to make a bucket and upload data to it.</p><p>The CLI is not required to use Storj, but it definitely comes in handy. If you prefer, you can always use the web interface, which is clean and intuitive.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5xGrckqcjAFHbhwphSp2oQ@2x.png" /><figcaption>image by author</figcaption></figure><h3>3. The Uplink CLI</h3><p>If you already have an account and do not have the uplink CLI installed, sign in and then click <strong>Quick Start</strong> and <strong>Upload using CLI</strong>. This will walk you through setting up the CLI along with an example of how to use it.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lodjMX_R_VsKRowNhZXWIw@2x.png" /><figcaption>image by author</figcaption></figure><h4>3.1 Uplink commands</h4><p>The Uplink CLI is simple to use with a short list of commands</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/798/1*nNVlGzoBn3Vz_oaQdNlMTw@2x.png" /><figcaption>image by author</figcaption></figure><p>The commands are intuitive. Here is a list of some basic operations.</p><p><strong>Basic uplink commands</strong></p><ul><li>uplink mb bucket_name — Makes a bucket</li><li>uplink cp /file/path s3://bucket_name — copy to a bucket</li><li>uplink cp s3://bucket_name /file/path — copy from a bucket</li><li>uplink ls s3://bucket_name — list contents of a bucket</li><li>uplink share --url s3://bucket_name/file.txt — generate a sharable URL of a file in a bucket</li></ul><h3>4. Read Data into Python</h3><p>Once you have an account setup and some data stored on the network, let’s learn how to read it from the cloud into Python.</p><p>If you have used S3 buckets with Python in the past, then you have likely used the s3fs package. This same package can interact with Storj buckets.</p><pre>conda install s3fs -c conda-forge</pre><p>or</p><pre>pip install s3fs</pre><h4>4.1 Create an Access Grant</h4><p>In order for s3fs to interact with Storj you will need to create an access grant. To do this, log into Storj and click on <strong>Access </strong>in the left then <strong>Create Access Grant. </strong>Follow the prompted steps. When you get to the permissions step, be sure to click <strong>Continue in Browser</strong> and when you get to the last step, click <strong>Generate S3 Gateway Credentials</strong> at the bottom.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/809/1*696gvoOwoRxBwPOAyggUOw@2x.png" /><figcaption>image by author</figcaption></figure><p>Your credentials will be prompted</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/468/1*ED1Z_KiyPxAaEWhtt3I5ZA@2x.png" /></figure><p>Save these fields to file or keep this page open for a moment.</p><p>Open Python and copy these fields into their appropriate place in the code snippet below. This snippet is giving you access to your Storj DCS account from Python.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/620/1*OQOjyy2fuOnFzrFda8kW_w@2x.png" /><figcaption>image by author</figcaption></figure><blockquote>Best solution is to store the secret keys in a dot file so it is not exposed in a script.</blockquote><p>Now try listing the contents of one of your buckets</p><pre>s3.ls(bucket_name)</pre><h4>4.2 Read a JSON file</h4><p>Here is one way to read a JSON file stored in a bucket</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/676/1*EDXfEy9Vx2QX6lRY6Mui6Q@2x.png" /><figcaption>image by author</figcaption></figure><h4>4.3 Read CSV file with Pandas</h4><p>Pandas makes it easy to read a CSV directly from cloud storage.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/588/1*cD7vMrHeY-PvEJfqAxcO-g@2x.png" /><figcaption>image by author</figcaption></figure><blockquote>Don’t forget the storage options (see section 4.1)</blockquote><h4>4.4 Read Multidimensional Data with Xarray</h4><p>Zarr files are cloud-optimized storage of multidimensional data. Reading data from zarr looks and feels identical to reading traditional netCDF files.</p><p>Xarray makes it easy to convert NetCDF output to zarr, which are easier to read from object storage than NetCDF.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/500/1*djVHLC3Tj1WH1OHzHfuKWA@2x.png" /><figcaption>image by author</figcaption></figure><p>Once you have a zarr file, you can upload it to Stroj using the following:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/500/1*2xmC91LA34lSZlmA1YC4GQ@2x.png" /><figcaption>image by author</figcaption></figure><blockquote>Don’t forget the storage options (see section 4.1)</blockquote><p>Reading a zarr file can be accomplished simply by</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/660/1*Hu5uNaIEdkyPbXP7S8KaxQ@2x.png" /><figcaption>image by author</figcaption></figure><blockquote>Don’t forget the storage options (see section 4.1)</blockquote><p>It is possible to read a NetCDF file from cloud storage, but it feels a little awkward.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/660/1*-z9G05cOziTYaRghvQUxNA@2x.png" /><figcaption>image by author</figcaption></figure><blockquote>I do not recommend uploading and reading NetCDF files to cloud object storage. <a href="https://pangeo.io/data.html#data-in-the-cloud">Zarr is a better solution</a>.</blockquote><h3>5. Conclusions</h3><p>Storj has matured over the years into a dead-simple object storage solution that offers a competitive free tier including 150 GB. Contrast this with the 5 GB <a href="https://aws.amazon.com/pm/serv-s3/?trk=ps_a134p000004f2aCAAQ&amp;trkCampaign=acq_paid_search_brand&amp;sc_channel=PS&amp;sc_campaign=acquisition_US&amp;sc_publisher=Google&amp;sc_category=Storage&amp;sc_country=US&amp;sc_geo=NAMER&amp;sc_outcome=acq&amp;sc_detail=%2Baws%20%2Bs3%20%2Bcloud%20%2Bstorage&amp;sc_content=S3%20Cloud%20Storage_bmm&amp;sc_matchtype=b&amp;sc_segment=488982706464&amp;sc_medium=ACQ-P|PS-GO|Brand|Desktop|SU|Storage|S3|US|EN|Text&amp;s_kwcid=AL!4422!3!488982706464!b!!g!!%2Baws%20%2Bs3%20%2Bcloud%20%2Bstorage&amp;ef_id={gclid}:G:s&amp;s_kwcid=AL!4422!3!488982706464!b!!g!!%2Baws%20%2Bs3%20%2Bcloud%20%2Bstorage">AWS S3</a> offers. If you need more storage, Storj will only charge $4 per TB each month, while the <a href="https://aws.amazon.com/s3/pricing/">AWS S3 price</a> would be around $23.</p><p>In addition to the competitive pricing, Storj is <a href="https://www.storj.io/blog/the-green-case-for-storj">working to reduce the environmental impact</a> of the Storj token and make its network more sustainable. The network will become more sustainable as Ethereum fully <a href="https://ethereum.org/en/upgrades/merge/">migrates to proof-of-stake</a>, which is expected in Q2 of 2022.</p><p>In addition, Storj cloud storage does not rely on building new data centers and did not cut down any trees or construct any new buildings to make its product. Instead, it relies on existing infrastructure.</p><p>Storj is not the only decentralized cloud storage provider. <a href="https://filecoin.io/">Filecoin</a>, which is built on IPFS and developed by Protocol Labs, is a buzzier product in the space. Filecoin is blockchain-based and relies on duplicating or “pinning” files to different nodes to secure the network. Duplication makes the network more durable but at the expense of copying the file many times. This differs from Storj which achieves durability through erasure encoding. Having said that, Filecoin and IPFS have their place in the <a href="https://web3.foundation/">web3 stack</a> and I encourage you to look into them. One Filecoin-based product I am intrigued by is <a href="http://web3.storage/">web3.storage</a>.</p><p>Finally, As much as I like the Storj DCS, I am aware it may not be the best solution for everybody. The pricing structure is simple and the storage cost is cheap, but if you are deeply embedded in the AWS ecosystem, then transitioning may not be the best solution.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=99d95ddc4e6a" width="1" height="1" alt=""><hr><p><a href="https://medium.com/better-programming/storj-decentralized-cloud-storage-my-new-favorite-cloud-object-storage-99d95ddc4e6a">Storj Decentralized Cloud Storage: My New Favorite Cloud Object Storage</a> was originally published in <a href="https://betterprogramming.pub/">Better Programming</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Why Lakes Turn Green]]></title>
            <link>https://medium.com/climate-conscious/why-lakes-turn-green-67c903d1a4b4?source=rss-780866762df------2</link>
            <guid isPermaLink="false">https://medium.com/p/67c903d1a4b4</guid>
            <category><![CDATA[lake-erie]]></category>
            <category><![CDATA[great-lakes]]></category>
            <category><![CDATA[farming]]></category>
            <category><![CDATA[policy]]></category>
            <category><![CDATA[science]]></category>
            <dc:creator><![CDATA[Luke Gloege, Ph.D.]]></dc:creator>
            <pubDate>Tue, 22 Feb 2022 13:02:36 GMT</pubDate>
            <atom:updated>2022-02-22T13:02:36.997Z</atom:updated>
            <content:encoded><![CDATA[<h4>How a whole-lake experiment answered the question</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*sAOLGb9gmNb9G1gegiv1TA.jpeg" /><figcaption>Lake 226 with an algal bloom in half the lake (photo credit: IISD Experimental Lakes Area (<a href="https://www.iisd.org/ela/">IISD-ELA</a>))</figcaption></figure><p>Have you ever wondered why many lakes turn unsightly green when it starts getting warm, especially those with heavy agricultural activity surrounding them?</p><p>Scientists set out to answer this question by conducting whole-lake experiments at the <a href="https://www.iisd.org/ela/">Experimental Lake Area</a> (ELA); a collection of 58 formerly pristine lakes set aside for scientific research. Located in Ontario about 150 miles North of International Falls, Minnesota.</p><h4>The Natural Aging Process</h4><p>All lakes have a natural aging process. Material carried into the lake via tributaries or wind will gradually fill the lake as it settles to the bottom, slowly turning the lake into a wetland; this natural process is called “<a href="https://www.open.edu/openlearn/nature-environment/environmental-studies/eutrophication/content-section-1.3">eutrophication</a>”. However, naturally going from young and pristine to old and marshy can take hundreds to thousands of years. Human intervention can speed up the natural process and it took a whole-lake experiment to get at the dominant factor.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/412/1*fuUDt663lXfp8vMduHcdjQ.png" /><figcaption>Phases of eutrophication. (image modified from <a href="https://dec.vermont.gov/sites/dec/files/wsm/lakes/docs/LMP%20Manual%20%282013%29.pdf">Vermont Lay Monitoring Program Manual</a>)</figcaption></figure><h4>The Experiment at Lake 226</h4><p>One of the most <a href="https://www.science.org/doi/pdf/10.1126/science.184.4139.897?casa_token=LuzyYvHZRIwAAAAA:HuyF13Wz0xA6CE8HXltIoqKkKpwLKodjPYN5W-nRVUq48Ecq7On62OznL1_eWjsEq0meKlHIsv7XIrA">famous experiments</a> at ELA began in 1973 when Lake 226 was physically partitioned with a vinyl-nylon sea curtain (see cover image). Over the course of eight years, the South end of the lake was fertilized with carbon and nitrogen while the North end was fertilized with carbon, nitrogen, and phosphorus.</p><p>The phosphorus side turned green due to a massive algal bloom, which demonstrated that phosphorus is a primary nutrient responsible for algal blooms. Excess phosphorus speeds up the natural aging process of a lake, termed “<a href="https://www.nature.com/scitable/knowledge/library/eutrophication-causes-consequences-and-controls-in-aquatic-102364466/">cultural eutrophication</a>.”</p><h4>Impact on Legislation</h4><p>At a time when nitrogen cleanup was underway in many communities, this landmark experiment convinced the public and policymakers that <a href="https://pubs.acs.org/doi/10.1021/acs.est.6b02204">phosphorus was the nutrient </a>that needed to be controlled and it paved the way for major legislative change.</p><p><a href="https://aslopubs.onlinelibrary.wiley.com/doi/pdfdirect/10.1002/lno.10055">Programs were implemented to control phosphorus</a> in sewage and remove phosphates from detergents, and a multi-million dollar phosphate control program was put in place in the Great Lakes. At the time, nitrogen cleanup was underway in many communities which this study showed was unnecessary. Communities were willing to change since phosphorous cleanup is cheaper than nitrogen.</p><h4>Cultural Eutrophication</h4><p>Cultural eutrophication is still a major problem in lakes with heavy agriculture activity surrounding them. For example, the western basin of Lake Erie is noticeably green in satellite images due to algal blooms. Lake Erie is commonly plagued with harmful algal blooms, which are forecasted annually by <a href="https://coastalscience.noaa.gov/research/stressor-impacts-mitigation/hab-forecasts/lake-erie/">NOAA.</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/560/1*r6zmQyAO5XW0_ZGrwHze5A@2x.png" /><figcaption>A European Space Agency (ESA) Envisat satellite image from Oct. 8, 2011, using its MERIS sensor. A harmful algal bloom is visible in the western basin of Lake Erie. (source: <a href="https://www.noaa.gov/media-release/noaa-partners-predict-large-summer-harmful-algal-bloom-for-western-lake-erie">NOAA</a>).</figcaption></figure><p>This is not only unsightly but in 2014 caused the <a href="https://greatlakes.org/2019/08/five-years-later-lessons-from-the-toledo-water-crisis/#:~:text=On%20August%202nd%2C%202014%2C%20residents,it%2C%20or%20brush%20their%20teeth.&amp;text=Five%20years%20ago%2C%20toxins%20from,water%20systems%20of%20Toledo%2C%20Ohio.">Toledo Water Crisis</a>, where residents were urged not to drink or use water from their tap because it was contaminated with highly toxic cyanobacteria.</p><p>The event prompted Ohio to pass laws prohibiting farmers from applying fertilizer when the ground is solid or waterlogged. However, the <a href="https://grist.org/food/ohio-officials-know-how-to-stop-lake-erie-from-turning-toxic-but-no-one-will-do-it/">impact of this legislation on water quality is unclear</a>.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FIJLCPedUiPg%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DIJLCPedUiPg&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FIJLCPedUiPg%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/e26bd9fa2bea3f22b9f435b754fc4c86/href">https://medium.com/media/e26bd9fa2bea3f22b9f435b754fc4c86/href</a></iframe><h4>Where do we go from here?</h4><p>The overuse of fertilizer is one of the major contributors to cultural eutrophication. Solutions including cover crops, no-till systems, and buffers between crops and waterways have been proposed.</p><p>Farmers obviously don’t want to overuse fertilizer since it translates into higher costs. However, it is generally assumed that a certain amount will be lost so farmers typically apply a little extra to make sure that the net amount after loss is adequate for their crop.</p><p>Researchers from Imperial College in London sought out a solution to this issue. They have devised <a href="https://www.nature.com/articles/s43016-021-00416-4.epdf?sharing_token=pxNRXKWaMY4objfVTS31VtRgN0jAjWel9jnR3ZoTv0MWsuVS0476i1G59UzPB_xXrOPkrh_hu6gjUvYIDVg3Yi5FRTObNuDgdmWk81CPSkrOzufR0gfmfWowfb7WrSCBU1wJh4EKVKxtJDGzK3T2noPjDsSbgTs8_eG1-aZcJn0%3D">a sensor that measures ammonia</a> in soil and then uses an algorithm to determine how much fertilizer to apply and makes a prediction how the amount of fertilizer will change within a two-week time frame. While it is a few years away from coming into mainstream use, this sensor could help farmers efficiently apply fertilizer without the need to overuse, reducing their costs and simultaneously helping mitigate eutrophication of nearby waters.</p><h4>Conclusions</h4><p>The landmark study at Lake 226 demonstrated <em>why</em> we need to control phosphorus but now we have to figure out <em>how</em> to control it. Removal from wastewater and detergents has solved the direct part of the problem, but we still have the indirect agricultural runoff problem to address. Thus, the development of more efficient fertilizer application techniques and being cognizant of how much we are using will help us attain our goals.</p><p>In addition to improved farming practices, lake management policies need to adapt. The typical mantra “dilution is the solution to pollution” does not necessarily hold water in large lakes. For instance, depending on the time of year, pollutants entering a lake via a river can get coastally trapped, which can be <a href="https://iopscience.iop.org/article/10.1088/1748-9326/ab7f62/meta">detrimental to coastal ecosystem services</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=67c903d1a4b4" width="1" height="1" alt=""><hr><p><a href="https://medium.com/climate-conscious/why-lakes-turn-green-67c903d1a4b4">Why Lakes Turn Green</a> was originally published in <a href="https://medium.com/climate-conscious">Climate Conscious</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A Guide to Hosting Websites on IPFS]]></title>
            <link>https://medium.com/better-programming/a-guide-to-hosting-websites-on-ipfs-d2efad40ed3?source=rss-780866762df------2</link>
            <guid isPermaLink="false">https://medium.com/p/d2efad40ed3</guid>
            <category><![CDATA[web3]]></category>
            <category><![CDATA[blockchain]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[Luke Gloege, Ph.D.]]></dc:creator>
            <pubDate>Wed, 16 Feb 2022 17:13:17 GMT</pubDate>
            <atom:updated>2022-02-16T17:13:17.824Z</atom:updated>
            <content:encoded><![CDATA[<h4>How to host a site on the Inter-Planetary File System</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*mNPVYyficJY6CbL0" /><figcaption>Photo by <a href="https://unsplash.com/@cazault?utm_source=medium&amp;utm_medium=referral">David Menidrey</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>In this post, I will describe how to host a website on the Inter-Planetary File System (<a href="https://ipfs.io/">IPFS</a>), a peer-to-peer network for storing and accessing files, webpages, data, etc across a network of computers. Decentralized storage protocols, such as IPFS, will play an integral in the <a href="https://web3.foundation/about/">web3 technology stack</a>, helping to make the internet decentralized and immutable. I believe this nascent protocol will be an important component of the internet in the coming years.</p><p>There are many <a href="https://docs.ipfs.io/concepts/usage-ideas-examples/">uses for this protocol</a>, but in this post, I will focus solely on hosting a website. Keep in mind that the IPFS protocol is currently not natively supported in all browsers yet, which can limit the reach of your content. For those IPFS-ignorant browsers, we can simply route our webpage through a “<a href="https://docs.ipfs.io/concepts/ipfs-gateway/">gateway</a>.” This is what I will be showcasing in this post. As an example, here is my webpage hosted on IPFS routed through a gateway.</p><pre><strong>Table of contents</strong></pre><pre><a href="#b63c">0. A brief overview of IPFS</a><br>  <a href="#52b1">0.1 Content Identifiers (CID)</a><br>  <a href="#4164">0.2 Gateway Servers</a><br>  <a href="#8b69">0.3 Where to go from here</a><br><a href="#0ddc">1. Install IPFS CLI</a><br>  <a href="#c54b">1.1 Brief tutorial of IPFS</a><br><a href="#bd80">2. Upload your website to IPFS</a><br><a href="#01ea">3. Connecting IPFS to your own domain name</a><br>  <a href="#72dd">3.1 DNS link domain to IPFS</a><br>  <a href="#7882">3.2 What actually happens?</a><br><a href="#8d0a">4. Conclusions</a></pre><h3>0. A brief overview of IPFS</h3><p>One of the main differences between IPFS and the ubiquitous hypertext transfer protocol (HTTP) is the way data is referenced. HTTP references by <em>location</em> while IPFS references by <em>content. </em>When you enter a uniform resource locator (URL) into your browser (e.g. <a href="http://www.medium.com/">www.medium.com</a>) you are specifying the location of the content you want to access.</p><p>However, maybe the person next to you at the coffee shop has a copy of the content you are asking for. So why not get it from them? This is possible with IPFS where multiple “peers” can host the same content allowing you to retrieve it from any of them instead of a central server. Files on IPFS may not be stored in just one place but in many places around the world, and someday maybe even on other planets (hence the name inter-<em>planetary</em> file system).</p><p>When asking for content on IPFS, we specify <em>what</em> we want with a <a href="https://proto.school/anatomy-of-a-cid">content identifier (CID)</a>, regardless of where it is located. This CID is a cryptographically unique hash of the content. Thus, if the content changes then the CID will change. This is important to ensure the content was not tamped with.</p><p>IPFS makes the internet immutable and resilient. The CID is unique to the content and thus if the content is changed then a new CID is generated. The data is resilient since it is hosted on multiple computers, making it difficult to be completely removed from the network.</p><h4>0.1 Content Identifiers (CID)</h4><p>A CID is analogous to a URL. While a URL describes the location of a server, a CID describes the content itself, regardless of where it is located. Put simply, IPFS asks computers connected to you “<em>do you know where the content associated with this CID is located?</em>” To which a computer in the network will respond, “<em>yes, you can find the content at this server.</em>”</p><p>As an example, here is the CID to the IPFS Wikipedia mirror:</p><pre>ipfs://QmXoypizjW3WknFiJnKLwHCnL72vedxjQkDDP1mXWo6uco</pre><p>Just like a file system, we can navigate to the content we really want.</p><pre>ipfs://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq/wiki/Beatles.html</pre><p>The challenge with this system is that most browsers do not know how to resolve an IPFS address. Brave and Opera are two browsers with <a href="https://github.com/ipfs/in-web-browsers#ipfs-and-igalia-collaborate-on-dweb-in-browsers">native IPFS support</a>. For the IPFS-ignorant browsers, we have to either install the <a href="https://github.com/ipfs/ipfs-companion">IPFS companion</a> or rely on a “gateway”.</p><h4>0.2 Gateway servers</h4><p>A gateway is a middleman between your browser and IPFS. You make an HTTP request to a centralized gateway server that then fetches IPFS content.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/697/1*nLadTLs0Ih4VjXu6CSazjw.png" /><figcaption>Schematic of how an IPFS gateway works. Requests are routed through a centralized gateway server and then the content on IPFS is retrieved and sent back to the user. (image source: modified <a href="https://undraw.co/">UnDraw</a> image by author)</figcaption></figure><p>Gateways allow anybody to access IPFS content (regardless of your browser), the downside is that the server is centralized and thus prone to being off-line. This is one <a href="https://medium.com/pinata/the-ipfs-gateway-problem-64bbe7eb8170">problem</a> with gateway servers.</p><p>Although a gateway is centralized and could permanently go offline, the content itself is still preserved on IPFS. The gateway may be gone, but the content itself still persists— assuming it is <a href="https://docs.ipfs.io/concepts/persistence/#persistence-versus-permanence">pinned</a> to a node in the network.</p><p>As an example, here is the same Beatles page as before routed through the https://dweb.link gateway:</p><pre>https://dweb.link/ipfs/QmXoypizjW3WknFiJnKLwHCnL72vedxjQkDDP1mXWo6uco/wiki/Beatles.html</pre><h4>0.3 Where to go from here</h4><p>Now that we understand a little bit about IPFS and how it works, let’s install IPFS and host some of our own content!</p><h3>1. Install IPFS CLI</h3><p>On macOS, you can use brew to install IPFS</p><pre>brew install ipfs</pre><p>This will install the IPFS command-line interface (CLI).</p><p>Or you can download it directly from <a href="https://ipfs.io/">ipfs.io</a></p><p><a href="https://docs.ipfs.io/install/command-line/">Command-line | IPFS Docs</a></p><p>Ensure the CLI is installed by checking the version.</p><pre>ipfs --version</pre><h4>1.1 Brief tutorial of IPFS</h4><p>Before we can start using IPFS, we need to <a href="https://docs.ipfs.io/how-to/command-line-quick-start/#initialize-the-repository">initialize the repository</a></p><pre>ipfs init</pre><p>This creates files in ~/.ipfs to store settings and internal data.</p><p>We can connect to the network by <a href="https://docs.ipfs.io/how-to/command-line-quick-start/#take-your-node-online">starting the daemon </a>in a new session.</p><pre>ipfs daemon</pre><blockquote>The daeman can be started in the background as well. ipfs daemon &amp; , just don’t forget to stop it. On macOS, killall ipfs will stop the daemon.</blockquote><p>Keep the daemon running so you can view content in your browser.</p><p>let’s now add some content to the network and save the CID to a variable.</p><pre>CID=`echo &quot;I &lt;3 IPFS -$(whoami)&quot; | ipfs add -q`</pre><blockquote>This will always generate the same CID, even if I change $(whoami) to my actual username. This is because IPFS is hashing the content. Therefore, if the content doesn’t change, then the hash (i.e. CID) wont change.</blockquote><p>Use the following to view the CID of the content you just added</p><pre>echo $CID</pre><blockquote>On my machine, the CID is: QmTUoo1hN87Vq8rjK9XbJgE6W2ZpHQLshTq31ZsrSLnGWT</blockquote><p>You can view the content you just added with ipfs cat</p><pre>ipfs cat QmTUoo1hN87Vq8rjK9XbJgE6W2ZpHQLshTq31ZsrSLnGWT</pre><p>Try resolving the CID via a gateway (e.g. <a href="https://dweb.link/ipfs/CID.">https://dweb.link/ipfs/CID</a>). For example, here is how I could resolve my CID.</p><pre>https://dweb.link/ipfs/QmTUoo1hN87Vq8rjK9XbJgE6W2ZpHQLshTq31ZsrSLnGWT</pre><p>You should see your message displayed in the browser.</p><p>This is great! We can successfully upload content to IPFS and view it in a browser. Now let’s add a full webpage.</p><h3>2. Upload your website to IPFS</h3><p>You will need a website to host. Here, I am not going to do anything fancy and just use vanilla HTML, CSS, and JavaScript with the following structure.</p><pre>.<br>├── index.html<br>├── css<br>│   └── styles.css<br>├── img<br>│   └── image.png<br>└── javascript<br>    └── scripts.js</pre><p>Just like before, we can add the directory with ipfs add -r &lt;site_dir&gt;</p><pre>ipfs add -r .</pre><blockquote>The -r flag adds recurively and the “.” indicates we are running the command from the webpage directory.</blockquote><p>You will notice that each item in the directory tree has a CID. All we care about is the top-level CID pointing to the entire website.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mnCUsyEn7V8CpRjgfS1jKg.png" /></figure><p>Just like before, we can view the webpage via a gateway</p><pre><a href="https://ipfs.io/ipfs/QmdJcNS1M42rjBT6mnfEuE1oTqP5nGWt3msgFA9KH36a9e/">ipfs.io/ipfs/QmdJcNS1M42rjBT6mnfEuE1oTqP5nGWt3msgFA9KH36a9e/</a></pre><p>but you probably don’t want to be telling people you have a cool webpage at <a href="https://ipfs.io/ipfs/QmdJcNS1M42rjBT6mnfEuE1oTqP5nGWt3msgFA9KH36a9e/">ipfs.io/ipfs/QmdJcNS1M42rjBT6mnfEuE1oTqP5nGWt3msgFA9KH36a9e/</a></p><p>One solution is to purchase a domain name and link to content on IPFS.</p><h3>3. Connecting IPFS to your own domain name</h3><blockquote>This step requires a domain name that you own. I purchased my domain name through <a href="https://www.cloudflare.com/">Cloudflare</a>, but any provider should work.</blockquote><p>We will use <a href="https://docs.ipfs.io/concepts/dnslink/">DNSLink</a> to map the IPFS CID to our domain address.</p><h4>3.1 DNS link domain to IPFS</h4><p>You will need a DNS provider. I am using Cloudflare, but the basic instructions should hold for any DNS provider.</p><ol><li>Log into Cloudflare</li><li>Click on your active website and then on DNS in the left-hand column.</li><li>DNS management section, add a TXT and CNAME record.</li><li>Set the TXT record <em>name</em> to _dnslink.yourwebsite.com (make sure to replace yourwebsite.com with your own domain)</li><li>Set the TXT record<em> content</em> to dnslink=/ipfs/CID (make sure replace CID with your own)</li><li>Set the CNAME record to any IPFS gateway (e.g. gateway.ipfs.io )</li></ol><p>As an example, here is a screenshot of my TXT and CNAME records.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BqOX5XyJwF-c6fjKrGB6TA@2x.png" /><figcaption>image by author</figcaption></figure><blockquote>Ensure there isn’t a leading or trailing blank space when copying a CID</blockquote><p>Once you enter all the information, click <strong>save</strong> and you will have successfully hosted your website on IPFS.</p><h4>3.2 What actually happens?</h4><p>Your browser performs a DNS lookup to get the IP address of the gateway server. The gateway notices the request is coming from your webpage and it will look for the DNS entry with the IPFS address to the content. The gateway will retrieve the content from IPFS and return it to your browser.</p><h3>4. Conclusions</h3><p>In this post, I provided one solution to host a webpage on IPFS and have it be viewable view anybody. Hosting a website on IPFS is easy, but does require a little bit of work (and money) to have a human-readable domain name.</p><p>The solution works, but it is cumbersome to keep changing the IPFS address in the TXT record each time you update the webpage. As a solution to this, I encourage you to check out the <a href="https://docs.ipfs.io/concepts/ipns/">Inter-Planetary Name System</a> (IPNS). I also suggest checking out the <a href="https://github.com/marketplace/actions/upload-to-ipfs">IPFS GitHub Action</a> and <a href="https://unstoppabledomains.com/">Unstoppable Domains</a>, if you want a completely decentralized website with a human-readable domain name (at the time of writing, browser support is limited with this approach.)</p><p>Another issue with the solution is accessibility. To ensure your content is always available it is best to “pin” your content to multiple nodes. I encourage you to check out different <a href="https://docs.ipfs.io/how-to/work-with-pinning-services/">pinning services</a> which make this easy. <a href="https://www.pinata.cloud/">Piñata</a> is one such service where you can pin a lightweight website using a free account.</p><p>Hosting a website is one of many <a href="https://docs.ipfs.io/concepts/usage-ideas-examples/">use cases</a> that exist for this emerging technology. An obvious application is a decentralized <a href="https://web3.storage/">storage solution</a>.</p><p>I for one am extremely excited by this emerging technology and look forward to seeing what is in store for the future. If you want to learn more I encourage you to check out the<a href="https://docs.ipfs.io/"> IPFS docs</a> and browser the tutorials in this <a href="https://flyingzumwalt.gitbooks.io/decentralized-web-primer/content/">gitbook</a>.</p><h4>Want to Connect?</h4><p><a href="https://lukegloege.com/">L. Gloege</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d2efad40ed3" width="1" height="1" alt=""><hr><p><a href="https://medium.com/better-programming/a-guide-to-hosting-websites-on-ipfs-d2efad40ed3">A Guide to Hosting Websites on IPFS</a> was originally published in <a href="https://betterprogramming.pub/">Better Programming</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>