Giter Club home page Giter Club logo

hyparquet's Introduction

hyparquet

hyparquet parakeet

npm workflow status mit license dependencies coverage

Dependency free since 2023!

What is hyparquet?

Hyparquet is a lightweight, pure JavaScript library for parsing Apache Parquet files. Apache Parquet is a popular columnar storage format that is widely used in data engineering, data science, and machine learning applications for efficiently storing and processing large datasets.

Hyparquet allows you to read and extract data from Parquet files directly in JavaScript environments, both in Node.js and in the browser. It is designed to be fast, memory-efficient, and easy to use.

Demo

Online parquet file reader demo available at:

https://hyparam.github.io/hyparquet/

Features

  1. Performant: Designed to efficiently process large datasets by only loading the required data, making it suitable for big data and machine learning applications.
  2. Browser-native: Built to work seamlessly in the browser, opening up new possibilities for web-based data applications and visualizations.
  3. Dependency-free: Hyparquet has zero dependencies, making it lightweight and easy to install and use in any JavaScript project.
  4. TypeScript support: The library is written in jsdoc-typed JavaScript and provides TypeScript definitions out of the box.
  5. Flexible data access: Hyparquet allows you to read specific subsets of data by specifying row and column ranges, giving fine-grained control over what data is fetched and loaded.

Why hyparquet?

Why make a new parquet parser? First, existing libraries like parquetjs are officially "inactive". Importantly, they do not support the kind of stream processing needed to make a really performant parser in the browser. And finally, no dependencies means that hyparquet is lean, and easy to package and deploy.

Usage

Install the hyparquet package from npm:

npm install hyparquet

Reading Data

Node.js

To read the entire contents of a parquet file in a node.js environment:

const { asyncBufferFromFile, parquetRead } = await import('hyparquet')
await parquetRead({
  file: await asyncBufferFromFile(filename),
  onComplete: data => console.log(data)
})

Browser

Hyparquet supports asynchronous fetching of parquet files over a network.

const { asyncBufferFromUrl, parquetRead } = await import('https://cdn.jsdelivr.net/npm/hyparquet/src/hyparquet.min.js')
const url = 'https://hyperparam-public.s3.amazonaws.com/bunnies.parquet'
await parquetRead({
  file: await asyncBufferFromUrl(url),
  onComplete: data => console.log(data)
})

Metadata

You can read just the metadata, including schema and data statistics using the parquetMetadata function:

const { parquetMetadata } = await import('hyparquet')
const fs = await import('fs')

const buffer = fs.readFileSync('example.parquet')
const arrayBuffer = new Uint8Array(buffer).buffer
const metadata = parquetMetadata(arrayBuffer)

If you're in a browser environment, you'll probably get parquet file data from either a drag-and-dropped file from the user, or downloaded from the web.

To load parquet data in the browser from a remote server using fetch:

import { parquetMetadata } from 'hyparquet'

const res = await fetch(url)
const arrayBuffer = await res.arrayBuffer()
const metadata = parquetMetadata(arrayBuffer)

To parse parquet files from a user drag-and-drop action, see example in index.html.

Filtering by Row and Column

To read large parquet files, it is recommended that you filter by row and column. Hyparquet is designed to load only the minimal amount of data needed to fulfill a query. You can filter rows by number, or columns by name, and columns will be returned in the same order they were requested:

import { parquetRead } from 'hyparquet'

await parquetRead({
  file,
  columns: ['colB', 'colA'], // include columns colB and colA
  rowStart: 100,
  rowEnd: 200,
  onComplete: data => console.log(data),
})

Column names

By default, data returned in the onComplete function will be one array of columns per row. If you would like each row to be an object with each key the name of the column, set the option rowFormat to object.

import { parquetRead } from 'hyparquet'

await parquetRead({
  file,
  rowFormat: 'object',
  onComplete: data => console.log(data),
})

Advanced Usage

AsyncBuffer

Hyparquet supports asynchronous fetching of parquet files over a network. You can provide an AsyncBuffer which is like a js ArrayBuffer but the slice method returns Promise<ArrayBuffer>.

interface AsyncBuffer {
  byteLength: number
  slice(start: number, end?: number): Promise<ArrayBuffer>
}

You can read parquet files asynchronously using HTTP Range requests so that only the necessary byte ranges from a url will be fetched:

import { parquetRead } from 'hyparquet'

const url = 'https://hyperparam-public.s3.amazonaws.com/wiki-en-00000-of-00041.parquet'
const byteLength = 420296449
await parquetRead({
  file: { // AsyncBuffer
    byteLength,
    async slice(start, end) {
      const headers = new Headers()
      headers.set('Range', `bytes=${start}-${end - 1}`)
      const res = await fetch(url, { headers })
      return res.arrayBuffer()
    },
  },
  onComplete: data => console.log(data),
})

Supported Parquet Files

The parquet format is known to be a sprawling format which includes options for a wide array of compression schemes, encoding types, and data structures.

Supported parquet encodings:

  • PLAIN
  • PLAIN_DICTIONARY
  • RLE_DICTIONARY
  • RLE
  • BIT_PACKED
  • DELTA_BINARY_PACKED
  • DELTA_BYTE_ARRAY
  • DELTA_LENGTH_BYTE_ARRAY
  • BYTE_STREAM_SPLIT

Compression

Supporting every possible compression codec available in parquet would blow up the size of the hyparquet library. In practice, most parquet files use snappy compression.

Parquet compression types supported by default:

  • Uncompressed
  • Snappy
  • GZip
  • LZO
  • Brotli
  • LZ4
  • ZSTD
  • LZ4_RAW

You can provide custom compression codecs using the compressors option.

hysnappy

The most common compression codec used in parquet is snappy compression. Hyparquet includes a built-in snappy decompressor written in javascript.

We developed hysnappy to make parquet parsing even faster. Hysnappy is a snappy decompression codec written in C, compiled to WASM.

To use hysnappy for faster parsing of large parquet files, override the SNAPPY compressor for hyparquet:

import { parquetRead } from 'hyparquet'
import { snappyUncompressor } from 'hysnappy'

await parquetRead({
  file,
  compressors: {
    SNAPPY: snappyUncompressor(),
  },
  onComplete: console.log,
})

Parsing a 420mb wikipedia parquet file using hysnappy reduces parsing time by 40% (4.1s to 2.3s).

hyparquet-compressors

You can include support for ALL parquet compression codecs using the hyparquet-compressors library.

import { parquetRead } from 'hyparquet'
import { compressors } from 'hyparquet-compressors'

await parquetRead({ file, compressors, onComplete: console.log })

References

Contributions

Contributions are welcome!

Hyparquet development is supported by an open-source grant from Hugging Face 🤗

hyparquet's People

Contributors

ctranstrum avatar platypii avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

hyparquet's Issues

Offset is outside the bounds of the DataView

Hello, I am getting this error when trying to read my file:

RangeError: Offset is outside the bounds of the DataView
    at DataView.getInt32 (<anonymous>)
    at readRleBitPackedHybrid (webpack-internal:///(rsc)/./node_modules/hyparquet/src/encoding.js:258:27)
    at readDataPage (webpack-internal:///(rsc)/./node_modules/hyparquet/src/datapage.js:70:111)
    at readColumn (webpack-internal:///(rsc)/./node_modules/hyparquet/src/column.js:60:135)
    at eval (webpack-internal:///(rsc)/./node_modules/hyparquet/src/read.js:144:126)
    at async Promise.all (index 15)
    at async readRowGroup (webpack-internal:///(rsc)/./node_modules/hyparquet/src/read.js:203:5)
    at async parquetRead (webpack-internal:///(rsc)/./node_modules/hyparquet/src/read.js:56:31)

Code snippet:

    const numRows = Number(metadata.num_rows);

    let limitRows;
    if (limit > 0) {
      limitRows = Math.min(limit, numRows);
      appLogger.info({ limitRows, numRows }, "Limiting parsed data rows");
    }

    let parquetData = [];

    await parquetRead({
      file: arrayBuffer,
      onComplete: (rows) => (parquetData = rows),
      ...(limitRows && { rowEnd: limitRows }),
    });

I thought maybe it happens because I might be specifying limit that is out of range for the data length, but that's not the case since I see this log from the IF statement:

{"level":"INFO","time":"2024-04-10T12:53:42.207Z","limitRows":10,"numRows":1780,"msg":"Limiting parsed data rows"}

parquet parsing breaks down after certain file size

Unfortunately for me the parquet parsing breaks down after a certain file size.

I then get the following error:

encoding.js:258 Uncaught (in promise) RangeError: Maximum call stack size exceeded
    at readRleBitPackedHybrid (encoding.js:258:13)
    at readData (encoding.js:219:19)
    at readDefinitionLevels (datapage.js:153:55)
    at readDataPage (datapage.js:55:16)
    at readColumn (column.js:57:71)
    at read.js:152:24
    at async Promise.all (hyparquet/index 0)
    at async readRowGroup (read.js:209:3)
    at async parquetRead (read.js:53:25)

this is the python code that generated the files:

# these files still work
df_measure_narrow_encoded.loc[:125000,].to_parquet(f"{export_dir}df_still_working_in_parquet_125000.parquet.txt", index = False)
df_measure_narrow_encoded.loc[:125000,].to_csv(f"{export_dir}df_still_working_in_parquet_125000.csv", index = False, encoding='utf-8-sig')

# these files don't work
df_measure_narrow_encoded.loc[:125125,].to_parquet(f"{export_dir}df_not_working_in_parquet_125125.parquet.txt", index = False)
df_measure_narrow_encoded.loc[:125125,].to_csv(f"{export_dir}df_not_working_in_parquet_125125.csv", index = False, encoding='utf-8-sig')

I'm providing example files both as parquet as well as csv files for completeness (the parquet files have a .txt ending since github wouldn't let me upload parquet).

Here are the files that produce the described error
df_not_working_in_parquet_125125.csv
df_not_working_in_parquet_125125.parquet.txt

Here are the files that still work
df_still_working_in_parquet_125000.parquet.txt
df_still_working_in_parquet_125000.csv

It seems to be a file size issue since it was the number of rows (not which rows, since I tested different row sets) that lead to the error.

You can easily recreate the error in my fork of your project (I added a complete file read and consol.log of the data based on your example and the commented out \ TODO):
https://v4lue4dded.github.io/hyparquet/

Use the file selection import:
image

and you will get:
image

Columns are returned in file order instead of requested order

If the parquet file has the following columns: id, lastname, firstname, address1, address2, city, state, zip and I issue a call to readParquet with columns: ['firstname', 'lastname'] then I get back table data like [['Doe', 'John'], ['Smith', 'Sally']].

I understand why, but I find it unintuitive. I would assume that row[0] would be the firstname column.

I see that #21 already fiddled with assembling the columns in a certain order, so this should be a fairly straight-foward change.

And if we are worried about backward compatibility, this could be included as an option instead of the default, and we could use the rowFormat option in #25 if it gets merged, or something similar if not.

After read parquet file by parquetRead result object is not full

Hi,
version : ^1.1.0
I have file parquet compression SNAPPY, when

let fileData: any[][] = [];

	var asyncBuffer: AsyncBuffer = await asyncBufferFromUrl(url);
	await parquetRead({
		file: asyncBuffer,
		compressors: {
			SNAPPY: snappyUncompressor()
		},
		onComplete: (data): void => {
			fileData = data;
		}
	});

parsed result is not complete
22425501.parquet.zip
in unparsed structure missed Menu->categories->dishes->addons->items-> price, volume, ingredient (this object total unparsed) in source data for this object is present
from source file
���Dish extra 1����Dodatki do ≈õniadania��� � �ƒ•∆Ö������������������������Ì�����������<������ˇˇˇˇ���"�"�ÎÂĈ������������������������Ì�������ò��ò��Ö®ÿ��� �������������‡»������∂Ì�����21985972����21985948����22861716����22673128����22672907���X�X�≤£flº <����������861005����20997760����11742617����895653���(�(�ùÒôÊ��� �������������‡»������ˇˇ���‰����$�$�øÙª€�<����������UK����EN����PL���8�8�Ω≠â¿ ����������������h9∂l���������������§í$���� �� ��ùÄËÙ�<������ ���–¶–µ–±—É–ª—è����Onion����Cebula����asd ���≈ömietana 12%����–ö–∞—Ä—Ç–æ–ø–ª—è����Potato����Ziemnaik���<�<�‹®à¯�����������������h9∂l���������������à∆˙à�����
In parsed
ingredient: undefined In source value for this field is preset (it required field)
price: undefined, (some is filled some empty but in parsed i missed for all)
volume: undefined (some is filled some empty but in parsed i missed for all)

Write to parquet file?

Is it possible to write to parquet file using this library? (quickly checked the code, didn't see any write function).

Allow users to call readRowGroup

Hello,

First of all, thank you for implementing this package. Would you be able to export the readRowGroup function? This is useful for when we're implementing predicate pushdown to read specific row groups, without needing to calculate rowStart and rowEnd (which sometimes has issues when we need to pass in BigInts greater than MAX_SAFE_INTEGER).

Thank you

Async reads: onComplete returns column data in unpredictable order

Hi folks, this is an awesome library. It is simple and powerful. Nice job!

I was running into an issue when asynchronously reading larger parquet files where the onComplete handler was giving me back column data in a different order each time. I couldn't figure out a way to determine what order the columns were in. I think part of the problem is that my slice function is highly async and not guaranteed to come back in any particular order.

I looked through the code and didn't see anything that would guarantee the order of the async slice calls. One potential solution is that the column data be resolved via promise rather than pushed onto an array, for example on this line: https://github.com/hyparam/hyparquet/blob/master/src/read.js#L189

Something like this could be used: const groupColumnData = await Promise.all(promises)

In the meantime I was able to find the onChunk callback which does not seem to be well-documented. I was able to workaround the issue by collecting the chunks manually because they are emitted alongside the column name.

Has anyone encountered this issue before? Maybe I'm missing something.

`statistics.min_value` is `undefined` instead of `''`

It's not an important thing, but I found that for some parquet files, the statistics.min_value is undefined when the raw value is an empty string.

See the file https://huggingface.co/datasets/CohereForAI/aya_dataset/blob/main/data/train-00000-of-00001.parquet:

On https://huggingface.co/datasets/CohereForAI/aya_dataset/tree/main/data?show_file_info=data%2Ftrain-00000-of-00001.parquet
Capture d’écran 2024-05-14 à 14 02 34

>> metadata.row_groups[0].columns[0].meta_data.statistics 
{ 
	distinct_count: undefined
	is_max_value_exact: undefined,
	is_min_value_exact: undefined,
	max: undefined,
	max_value: "🇰🇬 Манас эпосу 📚\nАты да Манас турбайбы, заты да Манас турбайбы.\nӨзү да Манас турбайбы, сөзү да Манас турбайбы.\nАлмамбет болсо жолдошум, кан Манас болсо жолборсум.\nКасташкан жоодон жалтансам, мени кудай урбайбы, - деп зор дем менен жоого каршы аттанган баатыр\n\nа) Алмамбет \nб) Сыргак\nв) Чубак \nг) Серек\n\n",
	min: undefined,
	min_value: undefined,
	null_count: 0n
}

On https://hyparam.github.io/hyparquet/
Capture d’écran 2024-05-14 à 14 03 00

With pyarrow.parquet:

>>> from pyarrow import parquet
>>> parquet.read_metadata("train-00000-of-00001.parquet").row_group(0).column(0).statistics
<pyarrow._parquet.Statistics object at 0x7cb3d3f39a40>
  has_min_max: True
  min:
  max: 🇰🇬 Манас эпосу 📚
Аты да Манас турбайбы, заты да Манас турбайбы.
Өзү да Манас турбайбы, сөзү да Манас турбайбы.
Алмамбет болсо жолдошум, кан Манас болсо жолборсум.
Касташкан жоодон жалтансам, мени кудай урбайбы, - деп зор дем менен жоого каршы аттанган баатыр

а) Алмамбет
б) Сыргак
в) Чубак
г) Серек


  null_count: 0
  distinct_count: None
  num_values: 1000
  physical_type: BYTE_ARRAY
  logical_type: String
  converted_type (legacy): UTF8
>>> parquet.read_metadata("train-00000-of-00001.parquet").row_group(0).column(0).statistics.min
''

Timestamps being incorrectly parsed sometimes

Hi,
I've uploaded a parquet file (link) that contains some timestamped data, where trip_time and fall_time are the timestamp fields. The values in each column should be unique, which I've confirmed by looking at the data in a VS Code parquet viewer extension, and also parsing in Python using pyarrow. However when I parse the file using hyparquet there's a big block of rows that have trip_time: 2022-12-31T05:33:14.127Z and fall_time: 2022-12-31T05:33:19.167Z, even though these values only occur once in the data.

These times do occur in the data, but only on one record:
image

I used this code to test:

import * as fs from "fs"
import { parquetRead } from "hyparquet"

async function main() {
    const buffer = fs.readFileSync("timeline-cleaned.sp.parquet")
    const arrayBuffer = new Uint8Array(buffer).buffer
    const columnsToRead = ["trip_time", "fall_time", "iec_category_id"]
    await parquetRead({
        file: arrayBuffer,
        columns: columnsToRead,
        onComplete: data => {
            for (const record of data) {
                console.log(`${record[0].toISOString()} ${record[1].toISOString()} ${record[2]}`)
            }
            console.log(`read ${data.length} rows`)
        }
    })
}

main()

Excerpt of the output:

2022-04-11T20:31:57.423Z 2022-04-11T20:58:44.547Z 102020101
2022-12-31T03:53:03.250Z 2022-12-31T03:53:08.277Z 1010101
2022-12-31T05:33:14.127Z 2022-12-31T05:33:19.167Z 102020101
2022-12-31T05:33:14.127Z 2022-12-31T05:33:19.167Z 30201
2022-12-31T05:33:14.127Z 2022-12-31T05:33:19.167Z 102020101
2022-12-31T05:33:14.127Z 2022-12-31T05:33:19.167Z 1010101
2022-12-31T05:33:14.127Z 2022-12-31T05:33:19.167Z 102020101
2022-12-31T05:33:14.127Z 2022-12-31T05:33:19.167Z 1010101
2022-12-31T05:33:14.127Z 2022-12-31T05:33:19.167Z 102020101
2022-12-31T05:33:14.127Z 2022-12-31T05:33:19.167Z 30201
[continues]

This is using hyparquet 0.9.9.

Query API

This is where we'll discuss adding a Query API to allow users to efficiently retrieve the data they're looking for. Users can perform column chunk/page-level predicate pushdown manually using column and offset indexes, but this is quite labor-intensive and requires some knowledge of how parquet files work.

Ideally, users should be able to write simple queries that are analyzed and used to construct efficient query plans/predicate functions which evaluate column chunk and page statistics. We could also try to optimize data fetching, especially over the network, by making multirange queries (when supported or specified via a flag) and concatenating requests for (nearly) adjacent byte ranges.

User-defined predicate functions are another option (potentially just a lower-level API), which can be implemented with fairly little effort. This would allow users to define arbitrary predicate logic and would also be a valid way of implementing different query frontends (eg: the simple structured queries discussed earlier).

More efficient row filtering

I am using rowStart and rowEnd to filter rows which works as advertised, but I am seeing some performance problems. It looks like the library is assembling all of the data from relevant row groups and then slicing off the undesired portion after the fact. If I just want a single row but my row group size is relatively high (i.e. 1 GB), the heap size still gets very large. There doesn't seem much benefit to using rowStart or rowEnd.

Looking through the code , it seems like the library could avoid holding onto the rows that fall outside of the requested row window. Does this problem resonate at all? I wonder if there are any plans to make this more efficient. I might be able to get some bandwidth to help with a fix if it seems doable/useful.

README code isn't runnable

Copy pasted code from the readme

import { parquetMetadata } from 'hyparquet'

const metadata = parquetMetdata(arrayBuffer)

How do I load an arrayBuffer?

Get column names in output data

Hi,

I think this may be a very basic question but... when reading parquet data with parquetRead, I get an array of arrays like this:

[[0, "foo"], [1, "bar"]]

I know that I can get the parquet column names from the parquet metadata with parquetMetadata. But is there a simple way to get data as an array of objects with column names, suitable to use with d3 or Plot?

Something like the following?

[{column1: 0, column2: "foo"}, {column1: 1, column2: "bar"}]

Many thanks for your work on this project !

benchmark.js error on first run

I'm encountering an error when running benchmark.js for the first time. The code execution enters this section. The stack trace shows the error parquet file invalid (footer != PAR1) originating from parquetMetadataAsync.

Upon investigation, it appears the issue is due to stat.size from here being set incorrectly on the first run. However, when running benchmark.js for the second time, it correctly retrieves the size of example.parquet.

Do you have any ideas on how to fix this issue? I'm encountering something similar in my code

Some typescript errors in hyparquet.d.ts

When using hyparquet.d.ts in a TypeScript project, I get the following errors:

[1] node_modules/hyparquet/src/hyparquet.d.ts(25,8): error TS1040: 'async' modifier cannot be used in an ambient context.
[1] node_modules/hyparquet/src/hyparquet.d.ts(51,8): error TS1040: 'async' modifier cannot be used in an ambient context.
[1] node_modules/hyparquet/src/hyparquet.d.ts(51,57): error TS2304: Cannot find name 'AsyncBuffer'.
[1] node_modules/hyparquet/src/hyparquet.d.ts(51,70): error TS2371: A parameter initializer is only allowed in a function or constructor implementation.
[1] node_modules/hyparquet/src/hyparquet.d.ts(51,127): error TS2304: Cannot find name 'FileMetaData'.
[1] node_modules/hyparquet/src/hyparquet.d.ts(59,60): error TS2304: Cannot find name 'FileMetaData'.
[1] node_modules/hyparquet/src/hyparquet.d.ts(67,41): error TS2304: Cannot find name 'FileMetaData'.
[1] node_modules/hyparquet/src/hyparquet.d.ts(67,56): error TS2304: Cannot find name 'SchemaTree'.
[1] node_modules/hyparquet/src/hyparquet.d.ts(93,9): error TS2304: Cannot find name 'AsyncBuffer'.
[1] node_modules/hyparquet/src/hyparquet.d.ts(94,14): error TS2304: Cannot find name 'FileMetaData'.
[1] node_modules/hyparquet/src/hyparquet.d.ts(100,17): error TS2304: Cannot find name 'Compressors'.

Wrong type for onComplete when called with rowFormat: 'object'

The type change in #25 caused a lot of downstream type errors.

Previously in v1.1.1 there was only any[][] types returned to onComplete.

Then in v1.2.0 we updated the type to any[][] | Record<string, any> but any project which was passing an onComplete function with expected type any[][] was throwing a typescript error, and was not easy to fix.

In v1.2.1 I reverted the type change, but left the behavior of rowFormat: 'object' where it will return Record<string, any>[] instead of any[][]. This means the types are not consistent with behavior in that case.

I don't know how I want to fix this yet. But I refuse to break downstream projects that depend on the types.

I think I will start looking at making a new entry point to hyparquet. I want the rows to be returned as a promise instead of a callback. But this may also be a good opportunity to fix the awkwardness of the current parquetRead function.

I want to join in hyparam

Hello, I'm Ethan Tan, a skilled Full-Stack Developer and Machine Learning Engineer with over 8 years of experience in the industry. With a robust background in JavaScript, TypeScript, and WebAssembly, I have a proven track record of developing high-performance, scalable solutions for complex data processing tasks. I am enthusiastic about contributing to Hyperparam's innovative projects and helping advance the machine learning community through open-source contributions.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.