<?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 Tim Swena (Swast) on Medium]]></title>
        <description><![CDATA[Stories by Tim Swena (Swast) on Medium]]></description>
        <link>https://medium.com/@timswast?source=rss-9f385167fd32------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*2eO1dpxQlQluTPR4Bv2geA.jpeg</url>
            <title>Stories by Tim Swena (Swast) on Medium</title>
            <link>https://medium.com/@timswast?source=rss-9f385167fd32------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Wed, 24 Jun 2026 12:16:10 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@timswast/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[Creating a production-ready data pipeline with Apache Airflow and BigFrames]]></title>
            <link>https://medium.com/google-cloud/creating-a-production-ready-data-pipeline-with-apache-airflow-and-bigframes-bead7d7d164b?source=rss-9f385167fd32------2</link>
            <guid isPermaLink="false">https://medium.com/p/bead7d7d164b</guid>
            <category><![CDATA[bigquery]]></category>
            <category><![CDATA[bigframes]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[google-cloud-platform]]></category>
            <dc:creator><![CDATA[Tim Swena (Swast)]]></dc:creator>
            <pubDate>Fri, 25 Jul 2025 21:36:53 GMT</pubDate>
            <atom:updated>2026-06-10T18:51:12.442Z</atom:updated>
            <content:encoded><![CDATA[<p>In this post, I build a data pipeline in Python to ingest US Census data into BigQuery tables, using Apache Airflow and BigQuery DataFrames (aka BigFrames). I’ll demonstrate how utilizing Google Cloud Platform (GCP) operators and BigFrames can significantly reduce the load on Airflow’s orchestration nodes and enable robust data validation for quality assurance</p><figure><img alt="A panda flying a blue airplane branded as BigQuery DataFrames with a futuristic eco-city behind and a giant rainbow colored fan branded as Apache Airflow." src="https://cdn-images-1.medium.com/max/1024/0*pgUW_oFTBuCvkCD9" /><figcaption>Image generated with Gemini</figcaption></figure><p>BigFrames is an open source Python library offered by Google. BigFrames scales Python data processing by transpiling common Python data science APIs to BigQuery SQL. You can read more about BigFrames in the <a href="https://dataframes.bigquery.dev/">official BigFrames API reference</a> and can refer to the <a href="https://github.com/googleapis/google-cloud-python/tree/main/packages/bigframes">public git repository for BigFrames</a>.</p><h3>Why use BigFrames with Apache Airflow?</h3><p>Data pipelines are an essential component for modern analytics, machine learning, and AI. Apache Airflow is a popular open source platform to author, schedule, and monitor workflows. It is particularly useful for creating production data engineering pipelines. These pipelines are made up of nodes that depend on each other and form Directed Acyclic Graphs (DAGs for short). As an open source tool, there are many options for deploying Apache Airflow. In these examples, I use <a href="https://cloud.google.com/composer">Google Cloud Composer</a>, but the sample DAGs should work in any Airflow environment where you have <a href="https://airflow.apache.org/docs/apache-airflow-providers-google/stable/connections/gcp.html">configured a Google Cloud connection</a>.</p><p>Unfortunately, common data engineering practices such as using pandas to transform data can overload the Airflow orchestration nodes, causing slower pipelines and delays or even out-of-memory crashes. BigFrames is “drop-in compatible” with pandas in many workloads by just changing the imports to <a href="https://dataframes.bigquery.dev/reference/api/bigframes.pandas.html">bigframes.pandas</a>. This keeps the data and the associated processing (<a href="https://dataframes.bigquery.dev/reference/api/bigframes.pandas.DataFrame.assign.html#bigframes.pandas.DataFrame.assign">transformations</a>, <a href="https://dataframes.bigquery.dev/reference/api/bigframes.pandas.DataFrame.merge.html#bigframes.pandas.DataFrame.merge">joins</a>, <a href="https://dataframes.bigquery.dev/reference/api/bigframes.pandas.DataFrame.groupby.html#bigframes.pandas.DataFrame.groupby">aggregations</a>, <a href="https://dataframes.bigquery.dev/reference/api/bigframes.bigquery.ai.html#module-bigframes.bigquery.ai">AI enhancement</a>, <a href="https://dataframes.bigquery.dev/reference/api/bigframes.bigquery.ml.html">ML training</a> and <a href="https://dataframes.bigquery.dev/reference/api/bigframes.bigquery.ml.predict.html#bigframes.bigquery.ml.predict">prediction</a>, and <a href="https://dataframes.bigquery.dev/reference/api/bigframes.pandas.Series.all.html#bigframes.pandas.Series.all">validations</a>) in the scalable BigQuery engine and out of the Airflow orchestration nodes where it causes problems.</p><h3>The data engineering pipeline</h3><p>This post will focus on the typical data engineering scenario of (1) getting data from some source — in this case the US Census, (2) cleaning it up, (3) validating the data is good quality, and (4) loading it into the final destination in your data warehouse / data lake. Since this pipeline will technically load data into BigQuery as temporary resources <em>before</em> transforming the data, this pipeline is what’s called an ELT pipeline rather than an ETL pipeline.</p><figure><img alt="A flow chart of the DAG with two nodes representing the operators: read from HTTPS and run BigFrames. BigFrames points to BigQuery." src="https://cdn-images-1.medium.com/max/638/0*DJERExwSaFDC3mUP" /></figure><p>The combination of Airflow and BigFrames is very flexible. For example, my teammate <a href="https://medium.com/google-cloud/build-a-linear-regression-model-on-large-data-in-python-using-bigquery-dataframes-3d80a0ccf199">Shobhit has written how BigFrames can be used to train an ML model</a>. You can use his sample and the Airflow techniques I describe in this post to build a pipeline that trains an ML model, validates it, and then deploys it to production or does batch prediction with it to enrich a dataset.</p><p>In this post, I’ll focus specifically on the steps needed to run BigFrames. For complete, runnable DAG examples, please refer to the following samples in my code snippets GitHub repository:</p><ul><li><a href="https://github.com/tswast/code-snippets/blob/main/2025/census-data-airflow-bigframes/census_to_bigquery.py">census_to_bigquery.py</a> (demonstrates PythonOperator)</li><li><a href="https://github.com/tswast/code-snippets/blob/main/2025/census-data-airflow-bigframes/census_to_bigquery_venv.py">census_to_bigquery_venv.py</a> (demonstrates PythonVirtualenvOperator)</li></ul><h3>Getting the data from the US Census</h3><p>The US Census publishes many different datasets. In this pipeline, you’ll load the <a href="https://www.census.gov/data/tables/time-series/demo/popest/2020s-counties-detail.html">County Population by Characteristics: 2020–2024 dataset</a>, which are provided as CSV files over HTTPS.</p><p>BigQuery cannot read directly from HTTPS, so the first node in the DAG downloads the CSV file and uploads it to GCS. I use the BashOperator for simplicity.</p><pre>GCS_BUCKET = &quot;your-bucket-id&quot;<br>GCS_LOCATION = f&quot;gs://{GCS_BUCKET}/us-census/cc-est2024-agesex-all.csv&quot;<br><br>download_upload = bash.BashOperator(<br>    task_id=&quot;download_upload&quot;,<br>    bash_command=f&quot;&quot;&quot;<br>    wget https://www2.census.gov/programs-surveys/popest/datasets/2020-2024/counties/asrh/cc-est2024-agesex-all.csv -P ~;<br>    gcloud storage cp ~/cc-est2024-agesex-all.csv {GCS_LOCATION}<br>    &quot;&quot;&quot;,<br>)</pre><p>For larger dataset there are many options for getting data into Google Cloud Storage. For example, the Cloud Storage Transfer Service can <a href="https://cloud.google.com/storage-transfer/docs/create-url-list">copy files from public HTTPS URLs to a Cloud Storage bucket</a> and there are Apache Airflow operators for <a href="https://airflow.apache.org/docs/apache-airflow-providers-google/stable/operators/cloud/cloud_storage_transfer_service.html#clouddatatransferservicerunjoboperator">running a transfer service job</a>.</p><h3>Operators that can run BigFrames</h3><p>BigFrames can run anywhere that can run Python and has access to Google Cloud resources. This includes the KubernetesPodOperator, DockerOperator, ExternalPythonOperator, PythonVirtualenvOperator, and PythonOperator. The KubernetesPodOperator is especially useful to provide an isolated environment running separately from the orchestration nodes, but this comes with additional complexity. Since this post uses bigframes, there is less reason to isolate from the orchestration nodes because the data processing happens in the BigQuery engine.</p><p>To simplify deployment, I recommend using the PythonOperator if you are able to install additional Python libraries such as the bigframes package to your Airflow environment. From the servers where Airflow is running, install bigframes:</p><pre>pip install --upgrade bigframes==2.11.0</pre><p>In Cloud Composer, configure the packages in the <a href="https://console.cloud.google.com/composer/environments/">Composer product page in Google Cloud Console</a>.</p><figure><img alt="A screenshot showing to go to the PyPI tab and add the bigframes package." src="https://cdn-images-1.medium.com/max/1024/0*pLt5Eq7m5Cq7g5Nx" /></figure><p>Use the PythonVirtualenvOperator if you need to install the bigframes package in an isolated environment.</p><p>Once BigFrames is installed, you can use the BigFrames pandas compatible module. Import the <a href="https://dataframes.bigquery.dev/reference/api/bigframes.pandas.html"><strong>bigframes.pandas</strong></a> module in the Python operator you’ve chosen:</p><pre>def callable_python():<br>    import bigframes.pandas as bpd<br>    # Operator logic will go here.<br><br>bf_to_gbq = PythonOperator(<br>    task_id=&quot;bf_to_gbq&quot;,<br>    python_callable=callable_python,<br>)</pre><h3>Recommended safeguards</h3><p>I recommend first configuring a few BigFrames options to ensure the best performance:</p><ul><li><a href="https://dataframes.bigquery.dev/reference/api/bigframes._config.ComputeOptions.html#bigframes._config.ComputeOptions.maximum_result_rows"><strong>bpd.options.compute.maximum_result_rows</strong></a>: This setting acts as a safeguard, preventing inadvertent large downloads of data from BigQuery to your Airflow worker. If a BigFrames operation would result in more rows than this specified limit being downloaded, the operation will fail, thereby protecting your worker’s memory resources.</li><li><a href="https://dataframes.bigquery.dev/reference/api/bigframes._config.BigQueryOptions.html#bigframes._config.BigQueryOptions.ordering_mode"><strong>bpd.options.bigquery.ordering_mode</strong></a><strong> = “partial”</strong>: Activating this mode generally yields optimal performance for BigFrames operations by avoiding the pandas feature that causes the worst performance: a default, sequential ordering and index.</li><li><a href="https://dataframes.bigquery.dev/reference/api/bigframes._config.BigQueryOptions.html#bigframes._config.BigQueryOptions.project"><strong>bpd.options.bigquery.project</strong></a>: Depending on how you’ve configured your Google Cloud credentials, you may need to set this setting so that BigFrames knows which project to bill for the BigQuery queries it runs.</li></ul><p>BigFrames uses a <a href="https://cloud.google.com/bigquery/docs/sessions-intro">BigQuery session</a> to store temporary resources. A BigQuery session <a href="https://cloud.google.com/bigquery/docs/sessions-terminating#auto-terminate_a_session">automatically closes after 24 hours of inactivity</a>, but I recommend putting a try/finally clause with <a href="https://dataframes.bigquery.dev/reference/api/bigframes.pandas.close_session.html#bigframes.pandas.close_session"><strong>bpd.close_session()</strong></a> to explicitly close the session to save on storage costs of the temporary resources bigframes creates.</p><pre>bpd.options.bigquery.ordering_mode = &quot;partial&quot;<br>bpd.options.bigquery.project = &quot;my-project-id&quot;<br>bpd.options.compute.maximum_result_rows = 10_000<br><br>try:<br>    # Your logic here.<br>    ...<br>finally:<br>    bpd.close_session()</pre><h3>Reading a CSV with BigFrames</h3><p>BigFrames provides several pandas-compatible methods to read data. Where possible, I recommend setting <a href="https://dataframes.bigquery.dev/reference/api/bigframes.pandas.read_csv.html#bigframes.pandas.read_csv">bpd.read_csv(…, engine=”bigquery”)</a>. By loading with the BigQuery engine, you can avoid having to read the file into memory and use BigQuery to parse the file(s) and load them into a temporary table.</p><pre># Inside the `try` block above.<br>GCS_BUCKET = &quot;your-bucket-id&quot;<br>GCS_LOCATION = f&quot;gs://{GCS_BUCKET}/us-census/cc-est2024-agesex-all.csv&quot;<br><br>df = bpd.read_csv(GCS_LOCATION, engine=&quot;bigquery&quot;)</pre><h3>Cleaning the data with BigFrames</h3><p>The Census data has a “YEAR” column, but it’s not clear which year it refers to. You can use pandas-compatible methods to preprocess the data before writing it to the final destination. This can help cleanup the data to make it more useful and easier to understand.</p><pre># Perform preprocessing. For example, you can map some coded data<br># into a form that is easier to understand.<br>df_dates = df.assign(<br>    ESTIMATE_DATE=df[&quot;YEAR&quot;].case_when(<br>        caselist=[<br>            (df[&quot;YEAR&quot;].eq(1), datetime.date(2020, 4, 1)),<br>            (df[&quot;YEAR&quot;].eq(2), datetime.date(2020, 7, 1)),<br>            (df[&quot;YEAR&quot;].eq(3), datetime.date(2021, 7, 1)),<br>            (df[&quot;YEAR&quot;].eq(4), datetime.date(2022, 7, 1)),<br>            (df[&quot;YEAR&quot;].eq(5), datetime.date(2023, 7, 1)),<br>            (df[&quot;YEAR&quot;].eq(6), datetime.date(2024, 7, 1)),<br>            (True, None),<br>        ]<br>    ),<br>).drop(columns=[&quot;YEAR&quot;])<br><br># TODO(developer): Add additional processing and cleanup as needed.</pre><h3>Validating the data with BigFrames</h3><p>One of the benefits of using BigQuery DataFrames in your operators is that it makes it easy to perform data validations before committing to write the data to its final destination.</p><pre># Note: cache() is optional, but if any of the preprocessing above is<br># complicated, it hints to BigQuery DataFrames to run those first and<br># avoid duplicating work.<br>df_dates.cache()<br><br>row_count, column_count = df_dates.shape<br>assert row_count &gt; 0<br>assert column_count &gt; 0<br>assert not df_dates[&quot;ESTIMATE_DATE&quot;].hasnans<br><br># TODO(developer): Add additional validations as needed.<br># For example, conider comparing the distribution of data in DataFrame<br># with the distribution of the data in the destination and alerting if there is a <br># significant shift or unknown outliers.</pre><h3>Writing to BigQuery</h3><p>Writing the data to its final destination is as easy as calling <a href="https://dataframes.bigquery.dev/reference/api/bigframes.pandas.DataFrame.to_gbq.html#bigframes.pandas.DataFrame.to_gbq">bpd.DataFrame.to_gbq()</a>. Or you can use other pandas-compatible I/O methods such as <a href="https://dataframes.bigquery.dev/reference/api/bigframes.pandas.DataFrame.to_parquet.html#bigframes.pandas.DataFrame.to_parquet">bpd.DataFrame.to_parquet()</a> to write to GCS, instead.</p><pre>BIGQUERY_DESTINATION = &quot;your-gcp-project.airflow_demo.us_census_by_county2020_to_present&quot;<br><br># Now that you have validated the data, it should be safe to write<br># to the final destination table.<br>df_dates.to_gbq(<br>    BIGQUERY_DESTINATION,<br>    if_exists=&quot;replace&quot;,<br>    clustering_columns=[&quot;ESTIMATE_DATE&quot;, &quot;STATE&quot;, &quot;COUNTY&quot;],<br>)</pre><h3>A note on DAG construction</h3><p>In this sample, I put all of the BigFrames code in a single operator. This has the benefit of all temporary resources getting cleaned up at the same time, but one loses out on Airflow’s ability to retry failed operations separately, which can allow for a more robust pipeline.</p><figure><img alt="A flowchart showing a DAG with more nodes." src="https://cdn-images-1.medium.com/max/1017/0*VvNGru0-e5rnu1na" /></figure><p>To prevent data from being cleaned up when the method operator finishes, call to_gbq(). If you don’t supply a name argument, one will be automatically assigned. You can then use <a href="https://www.astronomer.io/docs/learn/airflow-passing-data-between-tasks/#xcom">Airflow’s XCom feature</a> to pass the table ID along to the next task. Make sure to create a node that cleans up the table that is created when the DAG finishes or else the table may live for up to 7 days before being automatically cleaned up by BigQuery.</p><pre>def preprocess(task_instance):<br>    # Imports and settings go here.<br><br>    try:<br>        df = bpd.read_csv(GCS_LOCATION, engine=&quot;bigquery&quot;)<br>        # Preprocessing code goes here.<br>        task_instance.xcom_push(key=&quot;census_preprocessed_table&quot;, value=df.to_gbq())<br>    finally:<br>        bpd.close_session()<br><br>def validate_and_write(task_instance):<br>    # Imports and settings go here.<br><br>    try:<br>        # Get the table ID from the previous step.<br>        bigquery_source = task_instance.xcom_pull(<br>            task_ids=&quot;bf_preprocess&quot;,<br>            key=&quot;census_preprocessed_table&quot;,<br>        )<br>        df = bpd.read_gbq(bigquery_source)<br>        # Validation code goes here.<br>        df.to_gbq(bigquery_destination, if_exists=&quot;replace&quot;)<br>    finally:<br>        bpd.close_session()<br><br>bf_preprocess = PythonOperator(<br>    task_id=&quot;bf_preprocess&quot;,<br>    python_callable=preprocess,<br>)<br><br>bf_validate_and_write = PythonOperator(<br>    task_id=&quot;bf_validate_and_write&quot;,<br>    python_callable=validate_and_write,<br>)<br><br>cleanup_preprocess_table = BigQueryDeleteTableOperator(<br>    task_id=&quot;cleanup_preprocess_table&quot;,<br>    deletion_dataset_table=&quot;{{ task_instance.xcom_pull(task_ids=&#39;bf_preprocess&#39;, key=&#39;census_preprocessed_table&#39; }}&quot;,<br>    # Always execute, even if the previous task failed.<br>    # https://stackoverflow.com/a/44441890/101923<br>    trigger_rule=&quot;all_done&quot;,<br>)<br><br>download_upload &gt;&gt; bf_preprocess &gt;&gt; bf_validate_and_write &gt;&gt; cleanup_preprocess_table<br>bf_preprocess &gt;&gt; cleanup_preprocess_table</pre><p>For a detailed example, checkout my code snippets GitHub repository:</p><ul><li><a href="https://github.com/tswast/code-snippets/blob/main/2025/census-data-airflow-bigframes/census_to_bigquery_split.py">census_to_bigquery_split.py</a></li></ul><h3>Conclusion</h3><p>The integration of BigQuery DataFrames with Apache Airflow provides data engineers with the means to construct more robust, scalable, and efficient data pipelines. By performing data transformations and validations directly within BigQuery, you gain several significant advantages:</p><ul><li><strong>Enhanced Safety:</strong> Minimize the risk of memory overloads on Airflow workers and execute crucial data validations <em>before</em> any impact on production systems.</li><li><strong>Greater Flexibility:</strong> Harness the extensive capabilities of BigQuery for complex transformations, unburdened by the memory constraints of individual Airflow workers.</li><li><strong>Familiar Workflow:</strong> Data engineers and analysts already proficient with pandas will find BigFrames a natural and intuitive extension for interacting with BigQuery data.</li></ul><p>This approach effectively delegates the intensive computational tasks to your data warehouse, allowing Airflow to focus on its core strength: precise and reliable workflow orchestration. Happy pipelining!</p><p>The BigFrames team would love to hear from you. If you would like to reach out, please send an email to: <a href="mailto:bigframes-feedback@google.com">bigframes-feedback@google.com</a> or by filing an issue at the<a href="https://github.com/googleapis/python-bigquery-dataframes/issues"> open source BigFrames repository</a>. To receive updates about BigFrames, subscribe to the <a href="https://docs.google.com/forms/d/10EnDyYdYUW9HvelHYuBRC8L3GdGVl3rX0aroinbRZyc/edit?resourcekey=0-QUsnpzF91gm9hsp04rSA6Q">BigFrames email list</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=bead7d7d164b" width="1" height="1" alt=""><hr><p><a href="https://medium.com/google-cloud/creating-a-production-ready-data-pipeline-with-apache-airflow-and-bigframes-bead7d7d164b">Creating a production-ready data pipeline with Apache Airflow and BigFrames</a> was originally published in <a href="https://medium.com/google-cloud">Google Cloud - Community</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Story Behind Overcast Data: How Historical Weather Insights Can Transform Your Plans]]></title>
            <link>https://timswast.medium.com/the-story-behind-overcast-data-how-historical-weather-insights-can-transform-your-plans-6bb9dc16e4ae?source=rss-9f385167fd32------2</link>
            <guid isPermaLink="false">https://medium.com/p/6bb9dc16e4ae</guid>
            <category><![CDATA[noaa]]></category>
            <category><![CDATA[wedding-planning]]></category>
            <category><![CDATA[bigquery]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[weather]]></category>
            <dc:creator><![CDATA[Tim Swena (Swast)]]></dc:creator>
            <pubDate>Sat, 22 Mar 2025 21:15:17 GMT</pubDate>
            <atom:updated>2025-03-22T21:15:17.431Z</atom:updated>
            <content:encoded><![CDATA[<p>It all started with a simple question from my wife: “What will the weather be like on our wedding day?” We were planning our wedding at Starved Rock State Park, and with the date set a year in advance, we were curious — and maybe a little nervous. Weather forecasts only cover about 10 days, and climate models focus on long-term trends, so how could we predict what to expect on our big day? Would it be sunny, snowy, or pouring rain? That question sparked my dive into historical weather data and eventually led me to create a tool to help others answer similar questions.</p><figure><img alt="wedding rings over a canyon on a chart with a pencil and a weather vane in the backyard." src="https://cdn-images-1.medium.com/max/1024/0*HbOW2FMstuSiRUaE.png" /></figure><p>Luckily, my work with the <a href="https://cloud.google.com/bigquery">Google BigQuery</a> team had introduced me to a wealth of public datasets, and one in particular caught my eye: the NOAA Global Summary of the Day (GSOD) database. This treasure trove of information contains daily weather records from thousands of stations worldwide, tracking everything from temperature and rainfall to wind speed. It’s like a time machine for weather, letting you see what conditions were like on any given day in the past hundred years or so.</p><p><strong>It’s important to note that this project is a personal side endeavor, developed on my own time and hosted using my own resources. Any information shared here is my own work and doesn’t necessarily reflect the views or endorsements of my employer.</strong></p><p>I dove into the data, crafting a query to pull weather records from stations near our venue. After some analysis, I had a clear picture of what to expect. For our wedding date, the average high was 37°F (3°C), and the average low was 18°F (-8°C). But since weather can be unpredictable, I also looked at the range of likely outcomes: about half the time, the low temperature fell between 10°F (-12°C) and 27°F (-3°C). This helped us prepare for the possibility of a chilly — or even snowy — day.</p><figure><img alt="A text summary of the weather in Oglesby Illinois on February 18." src="https://cdn-images-1.medium.com/max/1024/0*MMyUZ0oM0hA_rNEc.png" /><figcaption><em>Screenshot of an early version of </em><a href="https://www.overcastdata.com/"><em>overcastdata.com</em></a><em>.</em></figcaption></figure><p>This data helped us make smart decisions, like choosing the right attire and preparing for the possibility of snow. And sure enough, our wedding day was a beautifully temperate — but still snowy — wonderland! After that, I started using the tool for all kinds of planning. For example, when my wife and I were packing for our honeymoon in Spain and France, historical weather data helped us decide what to bring. But for a while, it remained a personal tool.</p><p>I realized this could be helpful for a lot more people. Anyone planning an outdoor event, or simply curious about their local weather history, could benefit. That’s why I teamed up with Mario Torres Jr., a former apprentice of mine from Google. He’s been instrumental in helping me make this tool more accessible. You can find him on LinkedIn: <a href="https://www.linkedin.com/in/mario-torres-jr">https://www.linkedin.com/in/mario-torres-jr</a>. Together, we’re working to make it easier for everyone to explore their local weather history.</p><p>Curious about the weather history in your area? Whether you’re planning a wedding, a vacation, or even a garden, Overcast Data can help. Visit <a href="https://overcastdata.com">overcastdata.com</a> to explore historical weather patterns and make your plans with confidence. What surprises will you uncover? We’d love to hear how you use the tool — share your stories with us!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6bb9dc16e4ae" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Introducing partial ordering mode for BigQuery DataFrames (bigframes)]]></title>
            <link>https://medium.com/google-cloud/introducing-partial-ordering-mode-for-bigquery-dataframes-bigframes-ec35841d95c0?source=rss-9f385167fd32------2</link>
            <guid isPermaLink="false">https://medium.com/p/ec35841d95c0</guid>
            <category><![CDATA[bigframes]]></category>
            <category><![CDATA[google-cloud-platform]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[bigquery]]></category>
            <category><![CDATA[pandas-dataframe]]></category>
            <dc:creator><![CDATA[Tim Swena (Swast)]]></dc:creator>
            <pubDate>Wed, 12 Mar 2025 14:32:56 GMT</pubDate>
            <atom:updated>2026-06-10T18:57:02.433Z</atom:updated>
            <cc:license>http://creativecommons.org/licenses/by/4.0/</cc:license>
            <content:encoded><![CDATA[<p>BigQuery DataFrames aka BigFrames is an open source Python library offered by Google. BigFrames scales Python data processing by transpiling common Python data science APIs to BigQuery SQL. You can read more about BigFrames in the <a href="https://dataframes.bigquery.dev/">official BigFrames API reference</a> and can refer to the <a href="https://github.com/googleapis/google-cloud-python/tree/main/packages/bigframes">public git repository for BigFrames</a>.</p><p>Set the <a href="https://dataframes.bigquery.dev/reference/api/bigframes._config.BigQueryOptions.html#bigframes._config.BigQueryOptions.ordering_mode">bigframes.pandas.options.bigquery.ordering_mode</a> = &quot;partial&quot; option to prevent BigQuery DataFrames from creating a deterministic ordering and sequential index. Later in this post, I show an example of where this has a 4,000,000x speed up in terms of bytes scanned.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QjE_5_J9-1cGwzq25Pe5Ug.jpeg" /><figcaption>This feature makes the pandas-like BigQuery DataFrames faster and more efficient. Image generated by Gemini&#39;s Imagen feature.</figcaption></figure><p>Without this option set, BigQuery DataFrames creates a sequential index over all rows to mimic pandas behavior. On large tables this can get expensive, as it forces a full table scan, unless row and column filters are provided as arguments to <a href="https://dataframes.bigquery.dev/reference/api/bigframes.pandas.read_gbq.html#bigframes.pandas.read_gbq">bpd.read_gbq</a>. Partial ordering mode allows BigQuery DataFrames to push down many more row and column filters. On large clustered and partitioned tables, this can greatly reduce the number of bytes scanned and computation slots used.</p><h3>A brief primer on indexes in pandas and BigQuery DataFrames</h3><p>In pandas_gbq, when you download a BigQuery table as a DataFrame, pandas assigns a default index to the rows base on the order they were downloaded.</p><pre>import pandas_gbq<br><br>df = pandas_gbq.read_gbq(<br>  &quot;bigquery-public-data.ml_datasets.penguins&quot;,<br>  project_id=my_project)<br>df</pre><pre>                                       species     island  culmen_length_mm  culmen_depth_mm  flipper_length_mm  body_mass_g     sex<br>0          Adelie Penguin (Pygoscelis adeliae)      Dream              36.6             18.4              184.0       3475.0  FEMALE<br>1          Adelie Penguin (Pygoscelis adeliae)      Dream              39.8             19.1              184.0       4650.0    MALE<br>2          Adelie Penguin (Pygoscelis adeliae)      Dream              40.9             18.9              184.0       3900.0    MALE<br>3    Chinstrap penguin (Pygoscelis antarctica)      Dream              46.5             17.9              192.0       3500.0  FEMALE<br>4          Adelie Penguin (Pygoscelis adeliae)      Dream              37.3             16.8              192.0       3000.0  FEMALE<br>..                                         ...        ...               ...              ...                ...          ...     ...<br>339        Adelie Penguin (Pygoscelis adeliae)  Torgersen              38.8             17.6              191.0       3275.0  FEMALE<br>340        Adelie Penguin (Pygoscelis adeliae)  Torgersen              40.9             16.8              191.0       3700.0  FEMALE<br>341        Adelie Penguin (Pygoscelis adeliae)  Torgersen              39.0             17.1              191.0       3050.0  FEMALE<br>342        Adelie Penguin (Pygoscelis adeliae)  Torgersen              40.6             19.0              199.0       4000.0    MALE<br>343        Adelie Penguin (Pygoscelis adeliae)  Torgersen              37.3             20.5              199.0       3775.0    MALE<br><br>[344 rows x 7 columns]</pre><p>The index for a particular row doesn’t change, even if you apply a row filter to that DataFrame.</p><pre>df[df[&quot;species&quot;].str.contains(&quot;Chinstrap&quot;)]</pre><pre>                                       species island  culmen_length_mm  culmen_depth_mm  flipper_length_mm  body_mass_g     sex<br>3    Chinstrap penguin (Pygoscelis antarctica)  Dream              46.5             17.9              192.0       3500.0  FEMALE<br>6    Chinstrap penguin (Pygoscelis antarctica)  Dream              46.9             16.6              192.0       2700.0  FEMALE<br>7    Chinstrap penguin (Pygoscelis antarctica)  Dream              50.5             18.4              200.0       3400.0  FEMALE<br>8    Chinstrap penguin (Pygoscelis antarctica)  Dream              49.5             19.0              200.0       3800.0    MALE<br>13   Chinstrap penguin (Pygoscelis antarctica)  Dream              47.0             17.3              185.0       3700.0  FEMALE<br>..                                         ...    ...               ...              ...                ...          ...     ...<br>115  Chinstrap penguin (Pygoscelis antarctica)  Dream              48.5             17.5              191.0       3400.0    MALE<br>119  Chinstrap penguin (Pygoscelis antarctica)  Dream              46.4             17.8              191.0       3700.0  FEMALE<br>120  Chinstrap penguin (Pygoscelis antarctica)  Dream              47.5             16.8              199.0       3900.0  FEMALE<br>121  Chinstrap penguin (Pygoscelis antarctica)  Dream              48.1             16.4              199.0       3325.0  FEMALE<br>123  Chinstrap penguin (Pygoscelis antarctica)  Dream              55.8             19.8              207.0       4000.0    MALE<br><br>[68 rows x 7 columns]</pre><p>Why does pandas do this? A big reason is that in pandas, when you combine two objects, you are actually joining on an index.</p><pre>s1 = pd.Series([1, 2, 3], index=[0, 2, 4], dtype=&quot;Int64&quot;)<br>s2 = pd.Series([4, 5, 6], index=[0, 1, 2], dtype=&quot;Int64&quot;)<br><br># Align the two Series by their respective indexes and then add.<br>s1 + s2<br># 0       5<br># 1    &lt;NA&gt;<br># 2       8<br># 4    &lt;NA&gt;<br># dtype: Int64</pre><p>By preserving the index after the filter, pandas can still join the results back to the original DataFrame. In a SQL system like BigQuery, to do the same one needs a unique key to join by. pandas provides such a key implicitly for all objects. (Aside: it is possible to create a pandas object where the index is not unique. The behavior when joining on the index can get a bit unpredictable in this case.)</p><p>As a pandas-like API, BigQuery DataFrames (bigframes) also creates a sequential index based on the row order if no other index, such as <a href="https://cloud.google.com/blog/products/data-analytics/join-optimizations-with-bigquery-primary-and-foreign-keys">a primary key</a>, is available. Just like pandas, BigQuery DataFrames uses this index to implicitly join DataFrame and Series objects.</p><p>To get determinism, BigQuery DataFrames hashes all columns to provide a total ordering of the rows<a href="https://friendliness.dev/2024/09/04/bigframes-ordering-mode-partial/#fn1">1</a>. To get a sequential index, BigQuery DataFrames uses the <a href="https://cloud.google.com/bigquery/docs/reference/standard-sql/numbering_functions#row_number">ROW_NUMBER() analytic operation</a>. Notably, in BigQuery DataFrames and pandas, the index values remain stable, even after row filters are applied.</p><pre>s = pd.Series([1, 2, 3, 4, 5, 6], dtype=&quot;Int64&quot;)<br>s[s % 2 == 0]<br># 1    2<br># 3    4<br># 5    6<br># dtype: Int64</pre><p>This allows two objects with different filters to be implicitly joined together, matching on the same rows as if the filter hadn’t been applied.</p><pre>s = pd.Series([1, 2, 3, 4, 5, 6], dtype=&quot;Int64&quot;)<br>s[(1 &lt; s) &amp; (s &lt; 6)]<br># 1    2<br># 3    4<br># 5    6<br># dtype: Int64<br><br>s[s % 2 == 0] + s[(1 &lt; s) &amp; (s &lt; 6)]<br># 1       4<br># 2    &lt;NA&gt;<br># 3       8<br># 4    &lt;NA&gt;<br># 5    &lt;NA&gt;<br># dtype: Int64</pre><p>While useful in some contexts, creating a sequential index is an expensive operation, especially on clustered and partitioned tables. The hash for deterministic ordering means that column filters don’t work as expected and the analytical ROW_NUMBER() operation over the whole table means that row filters don’t work as expected.</p><h3>Partial ordering mode and NULL indexes</h3><p>To prevent unexpected full table scans, BigQuery DataFrames warns with a bigframes.exceptions.DefaultIndexWarning when reading from partitioned and clustered tables with no index_col or filters defined. For tables without a suitable set of columns to use as the index_col, as of BigQuery DataFrames (bigframes) version 1.7.0, you can set index_col=bigframes.enums.DefualtIndexKind.NULL in <a href="https://dataframes.bigquery.dev/reference/api/bigframes.pandas.read_gbq.html">read_gbq()</a>.</p><p>When setting this option, the analytic ROW_NUMBER operation is removed, allowing row filters to be pushed down. This has the potential to save you a lot of bytes scanned in workloads on clustered and partitioned tables. The tradeoff is that now unrelated DataFrame and Series objects can no longer be implicitly joined. In some cases, BigQuery DataFrames can determine that two objects are derived from the same table expression, but this isn’t always possible.</p><p>As of BigQuery DataFrames (bigframes) version 1.12.0, you can also disable a default ordering by setting the <a href="https://dataframes.bigquery.dev/reference/api/bigframes._config.BigQueryOptions.html#bigframes._config.BigQueryOptions.ordering_mode">bigframes.pandas.options.bigquery.ordering_mode</a> = “partial” option. This turns off default sequential indexes and also sets the default index kind to NULL. This allows both row filters and column filters to work as expected.</p><h3>Trying it out</h3><p>Query the last few days of data from the <a href="https://console.cloud.google.com/bigquery?ws=!1m5!1m4!4m3!1sbigquery-public-data!2spypi!3sfile_downloads">bigquery-public-data.pypi.file_downloads</a> table, which is partitioned by the timestamp column and clustered by the project column with ordering_mode = “partial”. <em>Do </em><strong><em>not</em></strong><em> try this with the default sequential index</em>, as the table is 375+ TB total logical bytes large and growing.</p><pre>import datetime<br><br>import bigframes.exceptions<br>import bigframes.pandas as bpd<br><br># IMPORTANT: use partial ordering mode to allow filters to work as expected.<br>bpd.options.bigquery.ordering_mode = &quot;partial&quot;<br><br># Show a preview of the previous day&#39;s downloads.<br>pypi = bpd.read_gbq(&quot;bigquery-public-data.pypi.file_downloads&quot;)<br>last_1_days = (<br>    datetime.datetime.now(datetime.timezone.utc)<br>    - datetime.timedelta(days=1)<br>)<br>bigframes_downloads = pypi[<br>    (pypi[&quot;timestamp&quot;] &gt; last_1_days)<br>    &amp; (pypi[&quot;project&quot;] == &quot;bigframes&quot;)<br>]<br>bigframes_downloads[[&quot;timestamp&quot;, &quot;project&quot;, &quot;file&quot;]].peek()</pre><pre>                   timestamp    project                                               file<br>0  2025-03-12 10:45:41+00:00  bigframes  {&#39;filename&#39;: &#39;bigframes-0.22.0-py2.py3-none-an...<br>1  2025-03-12 10:34:06+00:00  bigframes  {&#39;filename&#39;: &#39;bigframes-0.22.0-py2.py3-none-an...<br>2  2025-03-12 10:39:20+00:00  bigframes  {&#39;filename&#39;: &#39;bigframes-0.22.0-py2.py3-none-an...<br>3  2025-03-12 10:38:37+00:00  bigframes  {&#39;filename&#39;: &#39;bigframes-1.40.0-py2.py3-none-an...<br>4  2025-03-12 10:42:25+00:00  bigframes  {&#39;filename&#39;: &#39;bigframes-1.40.0-py2.py3-none-an...</pre><p>If you remove <a href="https://dataframes.bigquery.dev/reference/api/bigframes._config.BigQueryOptions.html#bigframes._config.BigQueryOptions.ordering_mode">bpd.options.bigquery.ordering_mode</a> = &quot;partial&quot; (please don’t), then the query BigQuery DataFrames generates processes 376.27 TB. But with this option set, the query BigQuery DataFrames generated only processes 84.8 MB processed. This is 4,000,000+ times speedup in terms of cost for on-demand queries!</p><p>Check out the <a href="https://dataframes.bigquery.dev/notebooks/dataframes/pypi.html">Analyzing package downloads from PyPI with BigQuery DataFrames</a> notebook for a more detailed look at these data using ordering_mode = “partial”. Also, learn more about BigQuery DataFrames and its features at the <a href="https://cloud.google.com/bigquery/docs/use-bigquery-dataframes">Use BigQuery DataFrames</a> guide.</p><h3>Share your feedback</h3><p>Is this feature helpful to you? Is there some functionality you need that is missing? The BigFrames team would love to hear from you. If you would like to reach out please send an email to: <a href="mailto:bigframes-feedback@google.com">bigframes-feedback@google.com</a> or by filing an <a href="https://github.com/googleapis/google-cloud-python/issues">issue at the open source BigFrames repository</a>. To receive updates about BigFrames, subscribe to the <a href="https://docs.google.com/forms/d/10EnDyYdYUW9HvelHYuBRC8L3GdGVl3rX0aroinbRZyc/edit?resourcekey=0-QUsnpzF91gm9hsp04rSA6Q">BigQuery DataFrames aka BigFrames email list</a>.</p><ol><li>If the table contains duplicate rows, the order of duplicate rows isn’t deterministic, but since the data is identical, the order of the identical rows won’t affect the results. BigQuery DataFrames performs a double hash to prevent collisions.</li></ol><p><em>Copyright 2024. Released under </em><a href="https://creativecommons.org/licenses/by/4.0/"><em>Creative Commons Attribution License</em></a><em>. Originally posted September 4, 2024 to </em><a href="https://friendliness.dev/2024/09/04/bigframes-ordering-mode-partial/"><em>https://friendliness.dev/2024/09/04/bigframes-ordering-mode-partial/</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ec35841d95c0" width="1" height="1" alt=""><hr><p><a href="https://medium.com/google-cloud/introducing-partial-ordering-mode-for-bigquery-dataframes-bigframes-ec35841d95c0">Introducing partial ordering mode for BigQuery DataFrames (bigframes)</a> was originally published in <a href="https://medium.com/google-cloud">Google Cloud - Community</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Effective Beginner-Friendly Messaging for Dweb]]></title>
            <link>https://medium.com/offline-camp/effective-beginner-friendly-messaging-for-dweb-e6e46eaaa9d5?source=rss-9f385167fd32------2</link>
            <guid isPermaLink="false">https://medium.com/p/e6e46eaaa9d5</guid>
            <category><![CDATA[offline-first]]></category>
            <dc:creator><![CDATA[Tim Swena (Swast)]]></dc:creator>
            <pubDate>Fri, 14 May 2021 15:44:52 GMT</pubDate>
            <atom:updated>2021-05-14T15:44:52.468Z</atom:updated>
            <content:encoded><![CDATA[<p>In our session at Offline Camp 2019, we identified several problems with how the dweb is presented. Documentation often focuses on implementation details. While this is aligned with the <a href="https://coolguy.website/the-future-will-be-technical/">DIY culture of the dweb</a>, it can alienate beginners. As has been discussed before, <a href="https://medium.com/offline-camp/decentralization-is-not-enough-75b15b8bc230">decentralization is not enough</a>. Documents need to show how these technologies solve the problems that developers and end-users actually have.</p><figure><img alt="How to make Dweb accessible to beginners? (Image source: element5digital on unsplash)" src="https://cdn-images-1.medium.com/max/1024/1*eJn9eEX7Lx8UCfRf2tgW0g.jpeg" /></figure><h3>Definitions</h3><p>As a distributed community, there isn’t a definitive definition of “dweb”. In our discussion at Offline Camp, we identified several common features of “dweb” apps, such as the ability to distribute and verify web content no matter how you obtained it. With the web, you need to be “online”, at least initially, to download the content. With the dweb, no matter how you obtain a document, whether from a friend on your local network or copied from a <a href="https://en.wikipedia.org/wiki/Sneakernet">portable hard drive</a>, you’ll still be able to verify that the document is the one you expected and was not tampered with.</p><h3>Overwhelming vocabulary</h3><p>It’s difficult to know how to get started with the dweb. A beginner is confronted with many confusing terms, including “dweb” itself. In an introductory article, a beginner may encounter most of the following terms:</p><ul><li>DWeb</li><li>Distributed</li><li>Decentralized</li><li>P2P</li><li>Server</li><li>Client</li><li>Peer</li><li>Seed</li><li>Pub</li><li>Pub/Sub</li><li>Node</li><li>Hash</li><li>Content addressable</li><li>Local-first</li><li>Public key</li><li>Wallet</li><li>Blockchain</li><li>Data sovereignty</li><li>Discovery</li><li>Federated</li><li>Smart Contract</li></ul><p>But most developers don’t care about these details. What they do care about is user experience. By discussing the benefits of dweb technologies first, a developer can then be motivated to learn the necessary details to use it.</p><h3>Resilience</h3><p>Resilience is a benefit that most agreed is important. In this way, dweb technologies can be viewed as a digital “seat belt”. They provide the most value in situations where traditional web apps fail.</p><p>Even in western cities, network connectivity is not guaranteed. For example, a web app may fail in the subway or during a disaster that affects network infrastructure. Likewise, it is an economic reality that <a href="https://ourincrediblejourney.tumblr.com/">apps shut down due to acquisition or lack of funding</a>. A dweb app can also protect an end-user’s access to their data in both of these situations.</p><h3>Efficiency</h3><p>By removing round-trip access to data, dweb technologies can save on network costs. An efficient use of resources can even save energy, which is becoming an increasingly important factor in light of global climate change.</p><h3>Access</h3><p>As Nolan Lawson describes in <a href="https://medium.com/offline-camp/decentralization-is-not-enough-75b15b8bc230">Decentralization is Not Enough</a>, these benefits should not be limited to the nerds of the world. As dweb technologies are built, they need to be designed and documented in ways that are more accessible to those of us without PhDs in distributed systems.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e6e46eaaa9d5" width="1" height="1" alt=""><hr><p><a href="https://medium.com/offline-camp/effective-beginner-friendly-messaging-for-dweb-e6e46eaaa9d5">Effective Beginner-Friendly Messaging for Dweb</a> was originally published in <a href="https://medium.com/offline-camp">Offline Camp</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[90s Aesthetics in Modern Tech]]></title>
            <link>https://medium.com/offline-camp/90s-aesthetics-in-modern-tech-4084489ba119?source=rss-9f385167fd32------2</link>
            <guid isPermaLink="false">https://medium.com/p/4084489ba119</guid>
            <category><![CDATA[pixel-art]]></category>
            <category><![CDATA[offline-first]]></category>
            <category><![CDATA[offline-camp]]></category>
            <category><![CDATA[creativity]]></category>
            <dc:creator><![CDATA[Tim Swena (Swast)]]></dc:creator>
            <pubDate>Mon, 16 Dec 2019 15:40:49 GMT</pubDate>
            <atom:updated>2019-12-16T15:40:49.507Z</atom:updated>
            <content:encoded><![CDATA[<h4>Approaching a blank canvas, 16x16 pixels at a time</h4><p>I have the following goals for my personal projects:</p><ul><li>I want the end-product to be approachable.</li><li>I want it to feel effortless for me to make progress on the project.</li><li>I want the project to remain useful for a long time.</li></ul><p>In my lightning talk at Offline Camp Oregon 2019, I share how these goals led me towards 1990s aesthetics.</p><iframe src="https://cdn.embedly.com/widgets/media.html?url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DnpVt5jmWK9Q&amp;src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FnpVt5jmWK9Q&amp;type=text%2Fhtml&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/4b048a84cef13d2a7ab3f63c60df6952/href">https://medium.com/media/4b048a84cef13d2a7ab3f63c60df6952/href</a></iframe><p>These design principles have guided several of my recent projects. For example, my <a href="https://stickers.bananajuice.tech">pixel art emoji stickers</a> are clearly inspired by the 90s aesthetic.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QU7evOuSHbmCYW0BitvMUw.png" /><figcaption>Pixel art emoji stickers</figcaption></figure><p>Likewise, I craft the HTML for <a href="https://www.timswast.com/">my personal website</a> by hand, similar to what folks did in the early web.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7x3pVyNv35RelSJixGzqOA.png" /><figcaption>My personal website</figcaption></figure><p>In the next sections, I will explain how I was drawn towards the 90s aesthetics. In what ways do they support the goals that I outlined?</p><h3>Approachable</h3><p>I write blog posts for people to read. I don’t want to distract the reader with banners, ads, or even navigation. Forcing myself to use handwritten HTML helps me feel less temptation to add unnecessary fluff.</p><p>The reason that I draw cute pixel-art emoji is so that my friends and family members can enjoy and use them.</p><p>It&#39;s been argued that <a href="http://www.dinofarmgames.com/a-pixel-artist-renounces-pixel-art/">pixel art is not approachable to the modern audience</a>. In my experience, however, even the generations younger than me have developed an appreciation for it. Minecraft and indie games have kept the style alive, despite it no longer being necessary due to hardware restrictions.</p><h3>Effortless</h3><p>It&#39;s clear that some projects require more mental energy to progress than others. I find it quite easy — relaxing even — to pull out my iPad and draw, whereas making progress on a computer programming project requires few distractions for long stretches of time.</p><p>Even as an artist, I struggle with a blank canvas. Having too many options causes me to freeze up. It takes mental energy to decide what to do next. A common strategy to overcome this is to <a href="https://medium.com/offline-camp/creating-with-constraints-broad-strokes-ftw-8cb00c9bee4c">boost creativity by using design constraints</a>.</p><p>Why make pixel art? It&#39;s a restriction, based on discrete mathematics and the past. Restriction makes sharing my art a little less intimidating. There&#39;s only so far you can take 16 by 16 pixels.</p><p>Why make a blog post with static files? It&#39;s a restriction. I can spend less time configuring and more time writing.</p><h3>Future-proof</h3><p>In stepping back to a 90s aesthetic, I&#39;m developing works that would have been more-or-less usable on the software of 25 years ago. It&#39;s my hope that this means my HTML, GIFs, and PNG images will be usable for at least another 25 years.</p><h3>Modern technology</h3><p>Dat and IPFS are ideally suited to sharing static files. This means that it&#39;s easy to share my site with modern peer-to-peer technologies.</p><p>I&#39;m hopeful that as these technologies develop and gain popularity, the chance that my work disappears will go down. So long as someone finds what I&#39;ve made useful, it can stay alive on the distributed web.</p><p><strong><em>Editor’s Note</em></strong><em>: Participants at </em><a href="http://offlinefirst.org/camp"><em>Offline Camp</em></a><em> Oregon 2019 had diverse backgrounds and interests, ranging far beyond the Offline First approach that we came together to discuss. Through short passion talks, campers shared with us some of the hobbies, projects, and technologies that excite them. We’re sharing a taste of that passion with you here as a preview to our </em><a href="https://offlinecamp.typeform.com/to/V8aSWJ"><em>upcoming events</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4084489ba119" width="1" height="1" alt=""><hr><p><a href="https://medium.com/offline-camp/90s-aesthetics-in-modern-tech-4084489ba119">90s Aesthetics in Modern Tech</a> was originally published in <a href="https://medium.com/offline-camp">Offline Camp</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Announcing google-cloud-bigquery Version 1.17.0]]></title>
            <link>https://medium.com/google-cloud/announcing-google-cloud-bigquery-version-1-17-0-1fc428512171?source=rss-9f385167fd32------2</link>
            <guid isPermaLink="false">https://medium.com/p/1fc428512171</guid>
            <category><![CDATA[cloud-computing]]></category>
            <category><![CDATA[big-data]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[google-cloud-platform]]></category>
            <dc:creator><![CDATA[Tim Swena (Swast)]]></dc:creator>
            <pubDate>Mon, 29 Jul 2019 00:00:00 GMT</pubDate>
            <atom:updated>2019-09-25T21:29:44.964Z</atom:updated>
            <cc:license>http://creativecommons.org/licenses/by/4.0/</cc:license>
            <content:encoded><![CDATA[<h3>Announcing google-cloud-bigquery Version 1.17.0: Query Results to DataFrame 31x Faster with Apache Arrow</h3><h3>TL;DR</h3><p>Upgrade to the latest google-cloud-bigquery and google-cloud-bigquery-storage packages to download query results to a DataFrame 4.5 times faster compared to the same method with version 1.16.0. If you aren&#39;t using the BigQuery Storage API yet, use it to download your query results 15 times faster compared to the BigQuery API. (31 times, if you don’t mind using the default Arrow to pandas conversion.)</p><p>This speed-up also works with the pandas-gbq library. Update, the google-cloud-bigquery and google-cloud-bigquery-storage packages and install pyarrow, and set the use_bqstorage_api parameter to True.</p><h3>Code samples</h3><p>To use the faster method to download large results, use the BigQuery Storage API from your Python programs or notebooks.</p><h3>Before you begin</h3><ul><li>Enable the BigQuery Storage API: <a href="https://console.cloud.google.com/apis/library/bigquerystorage.googleapis.com">https://console.cloud.google.com/apis/library/bigquerystorage.googleapis.com</a></li><li>Install the latest versions of the necessary Python packages (including dependencies).</li><li>Steps for conda:</li></ul><pre>conda install --channel conda-forge &#39;google-cloud-bigquery&gt;=1.17.0&#39; \<br>  &#39;google-cloud-bigquery-storage&gt;=0.7.0&#39; \<br>  &#39;pandas-gbq&gt;=0.10.0&#39;</pre><ul><li>Steps for pip:</li></ul><pre>pip install --upgrade google-cloud-bigquery<br>pip install --upgrade google-cloud-bigquery-storage<br>pip install --upgrade pyarrow<br>pip install --upgrade google-cloud-core<br>pip install --upgrade google-api-core[grpcio]</pre><p>From a notebook environment, the steps are the same, just prepend ! to call to the shell. For example:</p><pre>!pip install --upgrade google-cloud-bigquery</pre><h3>Using pandas-gbq</h3><pre>import pandas_gbq</pre><pre>sql = &quot;SELECT * FROM `bigquery-public-data.irs_990.irs_990_2012`&quot;</pre><pre># Use the BigQuery Storage API to download results more quickly.<br>df = pandas_gbq.read_gbq(sql, use_bqstorage_api=True)</pre><h3>Using the BigQuery client library</h3><pre>import google.auth<br>from google.cloud import bigquery<br>from google.cloud import bigquery_storage_v1beta1</pre><pre># Create a BigQuery client and a BigQuery Storage API client with the same<br># credentials to avoid authenticating twice.<br>credentials, project_id = google.auth.default(<br>    scopes=[&quot;https://www.googleapis.com/auth/cloud-platform&quot;]<br>)<br>client = bigquery.Client(credentials=credentials, project=project_id)<br>bqstorage_client = bigquery_storage_v1beta1.BigQueryStorageClient(<br>    credentials=credentials<br>)<br>sql = &quot;SELECT * FROM `bigquery-public-data.irs_990.irs_990_2012`&quot;</pre><pre># Use a BigQuery Storage API client to download results more quickly.<br>df = client.query(sql).to_dataframe(bqstorage_client=bqstorage_client)</pre><h3>What’s next</h3><ul><li>Read the <a href="https://cloud.google.com/bigquery/docs/reference/storage/">BigQuery Storage API reference documentation</a>.</li><li>Read the <a href="https://cloud.google.com/bigquery/docs/bigquery-storage-python-pandas">official how-tos for using the BigQuery Storage API with pandas</a>.</li></ul><h3>New features</h3><p>In addition to faster performance, google-cloud-bigquery package version 1.17.0 adds a <a href="https://googleapis.github.io/google-cloud-python/latest/bigquery/generated/google.cloud.bigquery.table.RowIterator.html#google.cloud.bigquery.table.RowIterator.to_arrow">RowIterator.to_arrow()</a> method to download a table or query results as a<a href="https://arrow.apache.org/docs/python/generated/pyarrow.Table.html">pyarrow.Table</a> object.</p><p>Arrow provides a cross-language standard for in-memory, column-oriented data with a rich set of data types. It is fast to create a pandas DataFrame from an Arrow Table with the method. With the <a href="https://fletcher.readthedocs.io/en/latest/">fletcher library</a>, the Arrow Table can be used directly as the backing data structure of a pandas extension array.</p><h3>Better performance</h3><p>We tested the performance of downloading BigQuery table data to pandas DataFrame and Arrow Tables by sampling the <a href="https://pantheon.corp.google.com/marketplace/details/city-of-new-york/nyc-tlc-trips">bigquery-public-data.new_york_taxi_trips.tlc_green_trips_*</a> tables.</p><p>We then timed how long it took to download these data to a pandas DataFrame from a Google Compute Engine n1-standard-8 (8 vCPUs, 30 GB memory) machine. We used the following methods:</p><ul><li>A: to_dataframe() - Uses BigQuery tabledata.list API.</li><li>B: to_dataframe(bqstorage_client=bqstorage_client), package version 1.16.0 - Uses BigQuery Storage API with Avro data format.</li><li>C: to_dataframe(bqstorage_client=bqstorage_client), package version 1.17.0 - Uses BigQuery Storage API with Arrow data format.</li><li>D: to_arrow(bqstorage_client=bqstorage_client).to_pandas(), package version 1.17.0 - Uses BigQuery Storage API with Arrow data format.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*DyY2gPYWyJBzMipS.png" /><figcaption>BigQuery to Pandas performance across table sizes. (Lower values are better)</figcaption></figure><p>The speedup is quite stable across data sizes. Using the BigQuery Storage API with the Avro data format is about a 3.5x speedup over the BigQuery tabledata.list API. It’s about a 15x speedup to use the to_dataframe() method with the Arrow data format, and a 31x speedup to use the to_arrow() method, followed by to_pandas() with the BigQuery Storage API.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Vd-yc8tZUNzlxvsK.png" /><figcaption>BigQuery to Pandas speedup versus the original tabledata.list API. (Higher values are better)</figcaption></figure><p>In conclusion, the fastest way to get a pandas DataFrame from BigQuery is to call RowIterator.to_arrow(bqstorage_client=bqstorage_client).to_pandas(). The reason for this difference is that to_dataframe() converts each message into a DataFrame and then concatenates them into a single DataFrame at the end. Whereas to_arrow() converts each message to a RecordBatch and creates a Table at the end. The difference in time is likely because pandas.concat(dfs) can make copies, where as pyarrow.Table.from_batches() doesn&#39;t make any copies. Also, pyarrow.Table.to_pandas() is often zero-copy.</p><h3>Limitations</h3><p>This BigQuery Storage API does not have a free tier, and is not included in the BigQuery Sandbox. Because of these limitations, you are required to have a billing account to use this API. See the <a href="https://cloud.google.com/bigquery/pricing#storage-api">BigQuery Storage API pricing page</a> for details.</p><p>The BigQuery Storage API does not yet read from small anonymous query results tables.</p><p>There are restrictions on the ability to reorder projected columns and the complexity of row filter predicates. Currently, filtering support when serializing data using Apache Avro is more mature than when using Apache Arrow.</p><p><em>Originally published at </em><a href="https://friendliness.dev/2019/07/29/bigquery-arrow/"><em>https://friendliness.dev</em></a><em> on July 29, 2019.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1fc428512171" width="1" height="1" alt=""><hr><p><a href="https://medium.com/google-cloud/announcing-google-cloud-bigquery-version-1-17-0-1fc428512171">Announcing google-cloud-bigquery Version 1.17.0</a> was originally published in <a href="https://medium.com/google-cloud">Google Cloud - Community</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Pixel Art Park 5 — An Attendee’s Review]]></title>
            <link>https://timswast.medium.com/pixel-art-park-5-an-attendees-review-693c91ff77de?source=rss-9f385167fd32------2</link>
            <guid isPermaLink="false">https://medium.com/p/693c91ff77de</guid>
            <category><![CDATA[tokyo]]></category>
            <category><![CDATA[pixel-art-park]]></category>
            <category><![CDATA[pixel-art]]></category>
            <dc:creator><![CDATA[Tim Swena (Swast)]]></dc:creator>
            <pubDate>Sun, 30 Dec 2018 00:00:00 GMT</pubDate>
            <atom:updated>2022-06-13T20:28:46.952Z</atom:updated>
            <content:encoded><![CDATA[<h3>Pixel Art Park 5 — An Attendee’s Review</h3><p>Alexandra and I attended <a href="https://pixelartpark.com/2018/12/">Pixel Art Park 5 </a>in Tokyo on December 9, 2018. I loved seeing so many pixel art pieces, chiptunes, and retro-style video games in one place and meeting other artists.</p><p>We got to the venue shortly before the festival opened and joined a long line to enter! Organizers announced (in Japanese) that we needed ¥ 1,000 in cash to pay for a ticket. A few minutes past 11 am, we all streamed into the building.</p><p>Inside, Alexandra and I explored three stories worth of art, music, games, and merch. The ground floor was quite crowded, so we went up to the second floor shortly after buying our tickets and commemorative t-shirt!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*wt7yJcrXQE03Q-99.jpg" /></figure><p>In the basement floor, we played demos of indie retro-style video games and enjoyed the “8-bit balloon art”.</p><p>The booths on the ground floor included several illustrators and a station for buying and constructing fuse bead art. Chiptune music played in the background while the attendees shopped and crafted.</p><p>The top floor was the largest and where we spent the most time. We strolled through the several rows of booths, while musicians played live on-stage. I enjoyed meeting the artists and developers in-person whose worked I’ve “liked” online.</p><p>We bought t-shirts, prints, postcards, and pins from some of our favorite artists. Also for sale were books, handkerchiefs, CDs, and voxel sculptures. A few of the booths were so popular that people queued for many minutes to meet the artist and buy merchandise. I think people even purchased a separate ticket to meet Waneella in the morning before her booth opened to everyone in the afternoon.</p><p>In addition to selling items, the folks at the <a href="http://www.ymck.net/">YMCK</a> booth drew free <a href="http://www.ymck.net/pap5_likeness/">pixel art portraits</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*krctPtV8UEX8eExc.jpg" /></figure><p>I really like how cute Alexandra’s portrait turned out!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/300/0*V7kdi_eT8hygTdza.gif" /></figure><p>We played several games. Alexandra and I both attempted to fish as much garbage as we could out of the river in the <a href="https://twitter.com/GameHotel337">Hotel 337</a> minigame.</p><p>I met the developers of <a href="https://dotpicko.net/">dotpict, my favorite pixel art editor for mobile phones</a>, and played their <a href="https://play.google.com/store/apps/details?id=net.dotpicko.dotpictgames&amp;hl=en_US">rhythm-based minigame</a>.</p><p>We also met the developer of PICO-8 and Voxatron. It was crowded, but I enjoyed playing <a href="https://www.lexaloffle.com/voxatron.php">Voxatron</a> on the <a href="http://www.lookingglassfactory.com/">Looking Glass volumetric display</a> and <a href="https://www.lexaloffle.com/bbs/?tid=29353">Feed the Ducks</a> for <a href="https://www.lexaloffle.com/pico-8.php">PICO-8</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*rD_9Vds8aev4gsBO.jpg" /></figure><p>Feed the Ducks was very cute and funny. Alexandra was even inspired to try making her own game because of it.</p><p>After lunch, we came back to listen to more music and talks. We caught the last minute of Waneella’s talk. After that a few artists did some live drawing of a Christmas tree. Even though I couldn’t understand most of the banter, it was very entertaining to watch.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Hoybx_nHNVujgX3S.jpg" /></figure><p>We also stayed to listen to some more live music. Our favorite was <a href="https://robotprins.jimdo.com/">Robotprins</a>, which mixed guitar with chiptunes. We were uncertain when we saw the guitar, but it worked really well together with the beeps and bloops!</p><h3>Pixel Art Park as an English Speaker</h3><p>I wished my Japanese skills were better a few times throughout the day. When we first lined up to meet Waneella, we figured out that we needed a separate ticket, but I didn’t know enough Japanese to know when or where we could have bought one. Similarly, we missed much of Waneella’s talk because I couldn’t understand the printed schedule.</p><p>My interactions with the artists weren’t as in-depth as I would have liked, partly because of the language barrier. In some cases I was able to communicate that I like their game / app / art and had already installed it or followed them. I said かわいい (cute) for many of the pieces we saw, and the artists did seem to appreciate that.</p><h3>Conclusions</h3><p>Even if I were fluent at Japanese, I think this format isn’t the best for in- depth conversations and trading techniques. I would love for there to be a pixel art “unconference” for small group conversations before or after a future Pixel Art Park. There were so many talented people in one place, and I’d love to learn more from them.</p><p>I was encouraged to see a crowd of people expressing their love for pixel art. It was amazing to see how many talented artists there are, as well as how many fans (including myself) who are willing to buy physical items from artists.</p><p>Overall, I had a lot of fun at Pixel Art Park 5. I enjoyed playing the retro- style video games, and we filled a shopping bag with pieces purchased from artists that I admire. I hope to return to Pixel Art Park someday.</p><p><em>Originally published at </em><a href="https://www.timswast.com/blog/2018/12/30/pixel-art-park-5/"><em>www.timswast.com</em></a><em> on December 30, 2018.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=693c91ff77de" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Fox Emoji]]></title>
            <link>https://timswast.medium.com/fox-emoji-1183c0d6782b?source=rss-9f385167fd32------2</link>
            <guid isPermaLink="false">https://medium.com/p/1183c0d6782b</guid>
            <dc:creator><![CDATA[Tim Swena (Swast)]]></dc:creator>
            <pubDate>Sun, 02 Dec 2018 04:56:42 GMT</pubDate>
            <atom:updated>2018-12-02T04:56:42.664Z</atom:updated>
            <content:encoded><![CDATA[<p>A year ago, I drew fox emoji for Alexandra’s birthday. I’m grateful for custom emoji as a unique way to express our love for each other.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*X89KO7kfV6bHrhvq.png" /></figure><p>We’ve enjoyed these a year together. Now, I’d like to share our <a href="/blog/2018/12/01/fox-emoji/fox-pixel-art-emoji.zip">fox emoji pack (ZIP)</a> with everyone.</p><p><a href="https://www.timswast.com/blog/2018/12/01/fox-emoji/">www.timswast.com/blog/2018...</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1183c0d6782b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Reflections on #octobit 2018]]></title>
            <link>https://timswast.medium.com/reflections-on-octobit-2018-274eea357f67?source=rss-9f385167fd32------2</link>
            <guid isPermaLink="false">https://medium.com/p/274eea357f67</guid>
            <dc:creator><![CDATA[Tim Swena (Swast)]]></dc:creator>
            <pubDate>Thu, 08 Nov 2018 16:34:39 GMT</pubDate>
            <atom:updated>2018-11-08T16:34:39.937Z</atom:updated>
            <content:encoded><![CDATA[<p><a href="https://twitter.com/brunopixels/status/1041755248258437120">Octobit 2018</a> recently finished up. I’m quite pleased with how all the art I drew turned out!</p><figure><img alt="an outer space collage of pixel art" src="https://cdn-images-1.medium.com/max/600/0*I2tPunXhICLujwnt.png" /></figure><figure><img alt="a man with spiked hair in front of a hologram of a fish" src="https://cdn-images-1.medium.com/max/600/0*x8xAoUFwMOv0dYAH.gif" /></figure><p>I imagine a whole ecosystem of cyborgs and robots, living off of sunlight and the mineral-rich asteroids. For the indoor tiles, I imagine a punk ranch hand keeping an eye on the action through a hologram.</p><h4>Planning</h4><p>This was my third year participating in octobit (see: <a href="/blog/2017/10/31/octobit/">2017</a>, <a href="/blog/2016/11/03/octobit/">2016</a>). I’ve enjoyed each year and am very grateful to <a href="http://brunopixels.tumblr.com/">Bruno</a>, the organizer, for encouraging us all to participate.</p><p>This year was different from the previous years in a couple ways:</p><ul><li>Previously there had been no restrictions on style, other than pixel art (and a suggested palette). This year, <a href="https://twitter.com/brunopixels/status/1041755248258437120">the organizer suggested we draw in isometric perspective</a>.</li><li>Instead of a theme for each day, we could decide to draw each theme in any order.</li><li>Optionally, attempt to make our isometric tiles fit together as a whole.</li></ul><p>I followed <a href="https://twitter.com/brunopixels/status/1041755248258437120">Bruno’s advice to plan ahead</a> and sketched out a few ideas. Eventually, I decided on a “space ranching” theme.</p><figure><img alt="pencil sketch of a space theme" src="https://cdn-images-1.medium.com/max/600/0*z5gCudyTQ9-DEH8T.jpg" /><figcaption>Some of my planning sketches.</figcaption></figure><p>Space ranching has been on my mind for over 5 years, and octobit felt like a good opportunity to make some progress on the idea.</p><h4>Posting</h4><p>This year, I posted my artworks to my website first and then syndicated to social media (a.k.a. <a href="https://indieweb.org/POSSE">IndieWeb POSSE</a>) with <a href="http://help.micro.blog/2016/cross-posting-twitter/">a little help from micro.blog</a>.</p><p>In <a href="/blog/2016/11/03/octobit/">2016</a> and <a href="/blog/2017/10/31/octobit/">2017</a>, I posted to Twitter first and then all the various social networks. It was a daily chore, and it felt like it! This year felt much better.</p><p>I didn’t post every day, which contributed to the more relaxed feeling. There were days when I was able to draw ahead and queue up a few artworks. This meant that I could have a few less productive days without falling behind.</p><h4>Practicing</h4><p>A few things about the artwork didn’t go as well as I planned.</p><h4>Scale</h4><p>I drew the vehicles and planets at the same resolution, this meant that they seemed the same size. In isometric projection, each pixel represents the same amount of distance, no matter how far away it is from the viewer. This meant that if I wanted a cohesive drawing, either my planets should have been much larger or the vehicles should have been much smaller.</p><p>The final collage feels crowded. Space shouldn’t be like that. There should be a lot of whitespace between the objects to give a better sense of the overall scale. Right now it feels like a collection of toys.</p><h4>Lighting</h4><p>In each work, imagined light coming from the bottom-right, but I only did a so-so job of shading. I think the shading probably turned out best in the planets.</p><figure><img alt="Mars" src="https://cdn-images-1.medium.com/max/600/0*aO3bfWu-n5ImiAld.png" /></figure><figure><img alt="StarCraft
Zerg Planet" src="https://cdn-images-1.medium.com/max/600/0*NkB35nmxzDdm-EhB.png" /></figure><figure><img alt="Io, a
moon of Jupiter" src="https://cdn-images-1.medium.com/max/600/0*uqpNXWcgYXgj-OPk.png" /></figure><p>These were much easier to shade because they are basically spheres. I want to get better at shading shiny spaceships (the space shuttle is too matte)</p><figure><img alt="space
shuttle" src="https://cdn-images-1.medium.com/max/600/0*P0hBVYpuJcRB5mRG.png" /></figure><p>and woolly creatures (the sheep looks speckled, not wooly)</p><figure><img alt="space
sheep" src="https://cdn-images-1.medium.com/max/600/0*d2gMBIwYBGJkG_Tr.png" /></figure><p>I hope to study more before the next octobit, using tools like <a href="https://pixelart.academy/">Pixel Art Academy</a> to level-up my skills.</p><h4>Participating</h4><p>The main reason I join octobit each year is to be a part of the online pixel art community. Other artists there are friendly and making amazing artworks. Some of the other pixel artists whose work I enjoyed this past October include:</p><ul><li><a href="https://www.instagram.com/p/BoZtMtghShk/?utm_source=ig_web_button_share_sheet">Bruno’s isometric “Porco” the space pig comics</a>,</li><li><a href="https://twitter.com/SalamiChild/status/1058262243249315840">SalamiChild’s medieval tiles</a> (I love how SalamiChild does so much with so few pixels),</li><li><a href="https://twitter.com/johanpeitz/status/1057739143751626753">Johan Peitz’s cowboys and aliens PICO-8 cart</a>,</li><li><a href="https://twitter.com/cityscapegoat/status/1057688018176356354">Chris E’s cute pixel town</a>,</li><li><a href="https://www.instagram.com/p/BpS2C9Jl8uE/">The Artist Julian’s breathtaking environments</a>,</li><li><a href="https://twitter.com/ElenaNazaire/status/1056263210498691072">Elena Nazaire’s detailed autumn scenes</a> and <a href="https://twitter.com/ElenaNazaire/status/1055531335027175426">isometric art</a>,</li><li><a href="https://www.instagram.com/p/BopaGQDFxPg/">Wolve_Rain’s beautiful illustrations</a>,</li><li>and <a href="https://www.instagram.com/p/BordBn4BWOC/">marta.salamon’s lovely botanical witches</a>.</li></ul><p>Despite seeing my art alongside these great artists, this year felt lonelier than past years. I should have commented more on other artists’ posts throughout the month. I also wonder if I should engage in some of the <a href="https://lospec.com/pixel-art-communities">pixel art specific communities</a>. In the past, more artists on Twitter commented on each other’s works, but maybe people have moved on to somewhere else?</p><p>I was a little disappointed by micro.blog. It’s possible there were other artists participating in octobit or inktober there, but I didn’t see them. With no hashtags (or even an art-specific emoji tag), it’s impossible to discover people working on drawing challenges like octobit. Still, I’m glad I joined, because it encouraged me to post to my own site.</p><h4>Conclusion</h4><p>I had a great time participating in octobit this year, and I look forward to joining again in future years. Next time around, I will comment more on others artwork to feel more connected to the community. I plan to be even better at pixel art next year, too. :-)</p><p>Copyright 2018. Released under <a href="https://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution License</a>.</p><p><a href="https://www.timswast.com/blog/2018/11/08/octobit/">www.timswast.com/blog/2018...</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=274eea357f67" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Grey, you will be missed. Rest in peace.]]></title>
            <link>https://timswast.medium.com/grey-you-will-be-missed-rest-in-peace-a3e2ab693a0e?source=rss-9f385167fd32------2</link>
            <guid isPermaLink="false">https://medium.com/p/a3e2ab693a0e</guid>
            <dc:creator><![CDATA[Tim Swena (Swast)]]></dc:creator>
            <pubDate>Mon, 05 Nov 2018 05:45:37 GMT</pubDate>
            <atom:updated>2018-11-05T05:45:37.222Z</atom:updated>
            <content:encoded><![CDATA[<p>Grey, you will be missed. Rest in peace.</p><figure><img alt="a pixel art image" src="https://cdn-images-1.medium.com/max/600/0*T60rtVJCt1ole51O.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a3e2ab693a0e" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>